Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic template function where return type depends on template argument list

I am getting a "wrong number of template arguments (2, should be 1)" error that I can't really understand.

I have a class that provides some helper function for other types that want to interact with it, setting a first template parameter on which they must agree to be compatible with each other automatically on creation. In order to do this in a convenient, general way, I decided to go for a variadic template, that is intended to both be passed constructor arguments and additional template arguments the object type that is to be created takes:

template<typename INTERNAL_TYPE>
class Linker
{
  template< template<typename, typename ...> class INPUT_OBJ_TYPE, class ... TEMPLATE_ARGS, class ... CONSTRUCTOR_ARGS >
  std::shared_ptr< INPUT_OBJ_TYPE<INTERNAL_TYPE,TEMPLATE_ARGS ...> > getLinked( CONSTRUCTOR_ARGS ... args )
  {
    std::shared_ptr< INPUT_OBJ_TYPE<INTERNAL_TYPE,TEMPLATE_ARGS ...> > ptr = std::make_shared< INPUT_OBJ_TYPE<INTERNAL_TYPE,TEMPLATE_ARGS ...> >( args... );
    return ptr;
  }
};

This works for the following class:

template<typename INTERNAL_TYPE, typename SECOND_TYPE>
class TEST_CLASS_1
{};

That is, I can do the following:

Linker<int> container;
auto test_1 = container.getLinked<TEST_CLASS_1,double>();

Then I tried to do the same with another class that is only templated on one argument:

template<typename INTERNAL_TYPE>
class TEST_CLASS_2
{};
auto test_2 = container.getLinked<TEST_CLASS_2>();

But get the above mentioned error.. Why? Playing around with the code I figured as much as that it would compile for the second test (no more for the first one though) if I removed TEMPLATE_ARGS from the function declaration/definition. So I figured that the compiler at that point does not yet realize TEMPLATE_ARGS is empty for the second test and throws an error for having too many template arguments. So I thought I might need to use a trailing return type like

template< template<typename, typename ...> class INPUT_OBJ_TYPE,
          class ... TEMPLATE_ARGS, class ... CONSTRUCTOR_ARGS >
auto getLinked( CONSTRUCTOR_ARGS ... args )
    -> std::shared_ptr< INPUT_OBJ_TYPE<TREE_TYPE,TEMPLATE_ARGS ...> >

or using decltype but that didn't work either:

template< template<typename, typename ...> class INPUT_OBJ_TYPE,
          class ... TEMPLATE_ARGS, class ... CONSTRUCTOR_ARGS >
auto getLinked( CONSTRUCTOR_ARGS ... args )
    ->decltype(std::shared_ptr< INPUT_OBJ_TYPE<TREE_TYPE,TEMPLATE_ARGS ...> >())

Am I right about the problem? And how can I solve it?

Thanks a lot!

like image 904
bluewater2 Avatar asked Oct 30 '22 04:10

bluewater2


2 Answers

This might be a compiler bug.

A way to get what you want is to provide two functions, your original and another that doesn't have the non-deducible parameter pack:

template<typename INTERNAL_TYPE>
struct Linker
{
    template<
        template<typename, typename ...> class INPUT_OBJ_TYPE,
        class ... TEMPLATE_ARGS,
        class ... CONSTRUCTOR_ARGS
    >
    std::shared_ptr<
        INPUT_OBJ_TYPE<INTERNAL_TYPE,TEMPLATE_ARGS...>
    > getLinked( CONSTRUCTOR_ARGS ... args )
    {
        std::shared_ptr< INPUT_OBJ_TYPE<INTERNAL_TYPE,TEMPLATE_ARGS...> > ptr =
          std::make_shared< INPUT_OBJ_TYPE<INTERNAL_TYPE,TEMPLATE_ARGS...> >( args... );
        return ptr;
    }

    template<
        template<typename, typename ...> class INPUT_OBJ_TYPE,
        class ... CONSTRUCTOR_ARGS
    >
    std::shared_ptr<
        INPUT_OBJ_TYPE<INTERNAL_TYPE>
    > getLinked( CONSTRUCTOR_ARGS ... args )
    {
        std::shared_ptr< INPUT_OBJ_TYPE<INTERNAL_TYPE> > ptr =
          std::make_shared< INPUT_OBJ_TYPE<INTERNAL_TYPE> >( args...);
        return ptr;
    }
};
like image 184
Vaughn Cato Avatar answered Nov 02 '22 09:11

Vaughn Cato


It appears that the initial substitution of TEST_CLASS_2 is hitting the compiler's "every valid specialization requires an empty pack" code path - that is, the following template is ill-formed, no diagnostic required:

template<class... TArgs, class... CArgs>
std::shared_ptr<TEST_CLASS_2<INTERNAL_TYPE, TArgs...>> getLinked( CArgs... args )
{
   /* ... */
}

because every valid specialization of it requires TArgs to be an empty pack. The initial substitution into getLinked will indeed result in something similar, but of course, you didn't actually write that template, so I'm not convinced that there should be an error, but whatever.

A possible workaround is to defer substitution of INPUT_OBJ_TYPE until after deduction, by taking a tag type and making everything deduced:

template< template<class, class...> class, class... > struct tag {};

template< template<class, class...> class INPUT_OBJ_TYPE,
          class... TArgs, class... CArgs >
auto getLinked(tag<INPUT_OBJ_TYPE, TArgs...>, CArgs... args )
    -> std::shared_ptr<INPUT_OBJ_TYPE<INTERNAL_TYPE,TArgs...>> 
{
    /* ... */
}
like image 44
T.C. Avatar answered Nov 02 '22 11:11

T.C.