Sign in to follow this  
Juliean

Template argument deduction fails here...

Recommended Posts

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

Edited by Juliean

Share this post


Link to post
Share on other sites

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

Edited by markypooch

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.
 

Share this post


Link to post
Share on other sites
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.

Edited by Juliean

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

 

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.

 

Auto&& allowes that its a universal reference so it even maintains const correctness and accepts both pointer, reference and pass by value semantics.

Share this post


Link to post
Share on other sites
Auto&& allowes that its a universal reference so it even maintains const correctness and accepts both pointer, reference and pass by value semantics.

 

Yes, but this is kind of the problem. I do not want it to accept all semantics, only those I specified for each type. Passing in int* should result in a compile error, passing in Object& should result in a compiler error, and so on... Sure, I could patch the types inside my variant class, where the values passed here actually get stored (int* to int, Object& to Object*), but I'd like to keep the most simple interface for the type system. Just like unreal, were you get a compile error when you try to register an int* as an object member to their type system, only that I do not have their code generation tool so I have to enforce it manually via templates.

(I think I might be misunderstanding your point, or maybe I didn't explain my reason in a clear way).

 

 

 

 

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

 

That would be an option, but would require multiple overloads for each function, which I want to avoid at most points. IE. a class might have multiple constructors, each taking different parameters, one of them being such a type-argument, which would require duplication them. Generally I'd like to avoid this kind of duplication, I just cut down on it some time ago. Especially since all those overloads would pretty much do the same thing.

 

But I think I found a way on how to avoid multiple overloads, using an isValidSemantic-type trait:

template<typename Type>
struct isValidSemantic
{
    static constexpr bool value = true; // need to generate actual meaningful overloads
};

template<typename Type>
using CheckSemantic = typename std::enable_if<isValidSemantic<Type>::value, int>::type;

template<typename Type, CheckSemantic<Type> = 0>
void foo(Type v)
{
    // do stuff
}

I also cleaned up the verbose std::enable_if declaration and put it inside the template, because some of the functions have return values.

Seems to work & appears like a neat way to do this. Thanks for your help, really tipped me in the right direction :)

 

EDIT: Actually I didn't even need to overload isValidSemantic, it works just like I though in my idea with the assert:

template<typename Type>
struct isValidSemantic
{
    // CleanTypeSemantic matches the semantic of Type with by first removing const, & and *, and then resolving to the actual needed semantic
    static constexpr bool value = std::is_convertible<Type, CleanTypeSemantic<Type>>::value;
};

just instead of "is_same" I check for convertability, which is probably not needed, but could come in handy when the function somehow assumes const int& is passed instead of int, IE.

Edited by Juliean

Share this post


Link to post
Share on other sites

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)


Because the standard says it should, basically.

More specifically, what you're asking for is ambiguous. Consider:

template<typename T> struct Dummy { using type = T*; };
template<typename T> struct Dummy<T&> { using type = T*; };
template<typename T> struct Dummy<T*> { using type = std::remove_pointer_t<T>*; };

template<typename T>
void foo(typename Dummy<T>::type t);

int* i;
foo(i); // does this resolve to foo<int> or foo<int&> or foo<int*> ?

The first two seem obvious, but the pointer specialization should start illustrating how complex this can get. Making this work for more common interesting cases will require speculatively instantiating dozens and dozens of templates that aren't even going to be used. Even if the standard did make that legal, such code would likely incur a pretty substantial compilation speed loss, which is anathema to many industries _especially_ games.

Such deduction rules are surely possible. You'd have to figure out the rules (you can't standardize intuition!) first, and then find an algorithmic solution implementable by all the major C++ vendors that makes this feature worth using in the first place. Which is entirely plausible; nobody's done it yet though. :)

Share this post


Link to post
Share on other sites
To contradict myself a bit, there is this paper: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0293r0.pdf

It was rejected at Issaquah different reasons than I outlined above, but was received positively and might come back with some changes in the future.

I don't know that any compiler implementors were present for its discussion so the instantiation speed problems may not have been discussed.

Share this post


Link to post
Share on other sites

More specifically, what you're asking for is ambiguous. Consider:

 

 

Yeah, I started thinking about it and saw that it made sense, your example is already kind of like what I'm doing. I though it worked differently, but obviously I was wrong.

 

And while it sounds interesting that they may change it in the future, I actually belive the the solution I developed with Brother Bobs help is the actual correct way here. If you look at my use-case, there is only two possible outcomes:

 

1) Type == TypeSemantic<Type>. So if Type is int, TypeSemantic<Type> is int. If Type is an Object*, TypeSemantic<Type> is also an object.

2) Type != TypeSemantic<Type>. If I pass in an int*, this is a compile-error, and so forth.

 

Just using void foo(TypeSemantic<Type>) wouldn't have worked in the first place, since TypeSemantic<Object*> already is a compile error, so I'd have to use TypeSemantic<CleanType<Type>>... and since there is only the case where the type is equal, and the other case where there's a compile error, I think using SFINEA is the correct approach to the problem (and possibly also allows different extentions like being able to pass in Objects via &.

 

I'm still going to use the TypeSemantic-trait, but now instead of where I pass in values, just for retrieving values:

const int integer = variant.Get<int>(); // TypeSemantic<Type> Get(void) const
Object* pObject = variant.Get<Object>();

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this