Template argument deduction fails here...

Started by
13 comments, last by Juliean 7 years, 4 months ago

Hello,

very simple test case that showcases a problem I have:


template<typename Type>
struct Dummy
{
    using type = Type;
};

template<typename Type>
void dummyFunction(typename Dummy<Type>::type type)
{
}

void main(void)
{
    dummyFunction(1); // error: no instance of function "dummyFunction" fits argument list
    dummyFunction<int>(1); // compiles
}

The problem I have is that dummyFunction(1) won't compile, only when I explicitely specify the template type.

In practice, this is a more complex part of my RTT-system where "Dummy<Type>" would modify the signature of the type depending on what it receives, but this is as simple a case as I could reduce it to so you can test for yourself. "Just put the type yourself" is a lot of unnecessary overhead in my real use-case, so I would like to avoid it.

So my quest:

1) Why does template argument deduction fail here? I know it is due to using the Dummy::type, but why is this a problem?

2) Is there any way around this, that still lets me get away without having to put dummyFunction<Type>(X)? Or is this some limitation of template-metaprogramming that I was not aware of? (Hopefully I didn't just forget an typename, then I'm going to kick something...)

EDIT: Well

Advertisement

Good point. You'd think the compiler would be smart enough to deduce the usage at compile time, but it doesn't. I'm no C++ savant, but if I had to guess in this sample, even the number literal 1 has some type ambiguation under the hood. I mean in C++ I can assign one (and have it be valid) in all kinds of scenarios, like:


char info = 1;
bool derp = 1;

Marcus Hansen

In practice, this is a more complex part of my RTT-system where "Dummy<Type>" would modify the signature of the type depending on what it receives, but this is as simple a case as I could reduce it to so you can test for yourself. "Just put the type yourself" is a lot of unnecessary overhead in my real use-case, so I would like to avoid it.

What you want then is for the compiler to find a type Type such that Dummy<Type>::type resolves to int. The type Type is unrelated to the parameter type int and the compiler would have to instantiate Dummy with every possible type in order to find the ones where Dummy<Type>::type is an int. That is, as you can imagine, a quite unreasonable task. There could be some obscure and hidden type, somewhere, that specialize the Dummy template with using Type = int and that would necessarily have to be a legal type for Type.

1) Why does template argument deduction fail here? I know it is due to using the Dummy::type, but why is this a problem?

The template parameter is in a non-deduced context and the language simply doesn't allow deduction in this case. I imagine my argument above would be a reasonable reason for that.

2) Is there any way around this, that still lets me get away without having to put dummyFunction<Type>(X)? Or is this some limitation of template-metaprogramming that I was not aware of? (Hopefully I didn't just forget an typename, then I'm going to kick something...)

EDIT: Well

The problem I have is that dummyFunction(1) won't compile, only when I explicitely specify the template type.

Possible ways around it depends on use case and what other possible constraints you can impose on the types. But as it stands, it simply isn't a context where a template parameter can be deduced. For example, if you pass other parameter types to the function, Type being one of them, then those parameters can deduce the types:


template<typename Type>
void dummyFunction(typename Dummy<Type>::type type, Type other)
{ ... }

Now the parameter other can deduce the template parameter, and Dummy<Type> is instantiated accordingly.

Is there more to the compiler error? Can you get the compiler to tell you what argument type it thinks it needs?

edit: Alas, ninja'd.
Assuming Dummy<Type>::type will always be Type, you can just do

template <typename Type>
void dummyFunction(Type type)
{
}

You should probably make a separate traits-styled mechanic (using specialization) to do Type2Type lookup backwards too, that way your template function can lookup Type without having it as a template parameter directly.

What you want then is for the compiler to find a type Type such that Dummy::type resolves to int. The type Type is unrelated to the parameter type int and the compiler would have to instantiate Dummy with every possible type in order to find the ones where Dummy::type is an int. That is, as you can imagine, a quite unreasonable task. There could be some obscure and hidden type, somewhere, that specialize the Dummy template with using Type = int and that would necessarily have to be a legal type for Type.

Ah, I didn't look at it form that way. Now that actually makes sense, thanks for the explanation.

Assuming Dummy::type will always be Type, you can just do

I was going to say that this is a wrong assumption. In my real use-case, Dummy::type could eigther be Type, const Type& or Type*, depending on what is passed to the function. But upon further inspection I realized that my usage of the this template here indeed is superflous. What I need it for is i.e. this:


variant.GetValue<int>() // returns int;
variant.GetValue<MyClass>() // returns MyClass*

This is done via a type-trait struct like in the example. Now in the cases where the user passes the value themselves this is not necessary, I can really just put template<typename Type> void function(Type value); and just check via static_assert that the attribute semantics match with what is expected for the type.

Thanks to all, helped a lot.

Why not stick an "auto&&" in your function instead of the specific type, if you use a modern (C++14 compiler) that would work, and would also be easier to read.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

Why not stick an "auto&&" in your function instead of the specific type, if you use a modern (C++14 compiler) that would work, and would also be easier to read.

As I said, the original idea was to enforce a certain semantic for a certain type. IE:

- int, float, bool are passed as value

- std::string, std::vector<>, user-defined structs are passed via const reference

- user-defined objects are passed via pointer

so the idea was, that if I called such a function, it would enforce the correct semantic automatically in the function signature:


int x = 1;
dummyFunction(x); // works
dummyFunction(&x); // doesn't compile, int is only passed by value

Object* pObject = new Object();
dummyFunction(pObject); // works
dummyFunction(*pObject); // doesn't compile, objects are only passed by pointer

So if I just put auto&&, it would take whatever I pass to it (like int***** as an extreme case), and I didn't want that. Apparently this isn't possible with the type-trait template that I made. So what I'm now going to do is akin to this:


template<typename Type>
void function(Type type)
{
    // clearType-trait removes const, reference, pointer
    static_assert(std::is_same<Type, typename semantic<typename clearType<Type>::type>::type>::value, "Type is passed with an invalid signature.");
}

This should make sure that the signature fits the expected value for the underlying type, and since all calls of those type eventually come down to my variant-class, I can just put it there and be fine with it, I assume.

Maybe overloading and SFINAE can help you. Something like this; seems to work on VS2015. Tweak the type trait checks as you like.


template <typename T>
using is_vector = std::is_same<T, std::vector< typename T::value_type, typename T::allocator_type>>;

template<typename T>
typename std::enable_if<std::is_arithmetic<T>::value>::type foo(T v)
{ std::cout << "value: " << v << std::endl; }

template<typename T>
typename std::enable_if<is_vector<T>::value>::type foo(T const &v)
{ std::cout << "vector: " << v[0] << std::endl; }

template<typename T>
typename std::enable_if<std::is_class<T>::value>::type foo(T *v)
{ std::cout << "pointer: " << v << std::endl; }

int main()
{
    int i = 42;
    double d = 3.14;
    std::vector<int> v{271};
    std::string s = "foo";

    foo(i);     /* ok; arithmetic type by value */
    foo(d);     /* ok; arithmetic type by value */
    foo(v);     /* ok; vector by const reference */
    foo(&v);    /* ok; pointer to class type although class happens to be vector */
    foo(&s);    /* ok; pointer to class type */

#if 0
    foo(&i);    /* not ok; pointer to non-class type */
    foo(s);     /* not ok; class type but not called as pointer */
#endif
}

The value function is enabled only for arithmetic types, the vector function is enabled only for vectors (of any kind) and the pointer function is enabled for classes only. Just be aware that you can pass vectors by pointer to the pointer function since the vector is a class. There's no way to distinguish user defined classes from standard library classes, as far as I am aware, other than manually excluding them.

This topic is closed to new replies.

Advertisement