15

Assume the following situation:

Type A and type B, B can be implicitly converted to A but the opposite is untrue.

I have a function

template<class T>
void do_stuff(T a, T b);

I want to call said function as such:

do_stuff(A{}, B{});

The problem here is that the compiler can't deduce the type and instead says:

template argument deduction/substitution failed

I can call my function like this:

do_stuff<A>(A{}, B{});

But this is more annoying for the user.

Alternatively I can do something like this:

template<class T, class M>
void do_stuff(T a, M b);

But then b goes on its merry way to be of type B (with the prior invocation).

Ideally I would like something like:

template<class T, class M = T>
void do_stuff(T a, M b);

Or:

template<class T@INSERT MAGIC SO THAT T IS DEDUCED AS BEING THE TYPE OF ARGUMENT NR 1@>
void do_stuff(T a, T b);

Is such a thing possible ?

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
George
  • 3,521
  • 4
  • 30
  • 75

4 Answers4

21

Wrap b in a non-deduced context. That way, only a will be deduced and b must be converted to that type.

template <class T> struct dont_deduce { using type = T; };
template <class T> using dont_deduce_t = typename dont_deduce<T>::type;

template<class T>
void do_stuff(T a, dont_deduce_t<T> b);
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Ok... this is actually the answer I was looking for, but would you be so kind as to explain a bit the black magic going on in the first two lines ? Or source a reference that explains it. – George Jun 29 '17 at 21:08
  • 3
    @George See [What is a nondeduced context?](https://stackoverflow.com/q/25245453/2069064). We're just preventing `b` from being deduced, it's just treated as an argument whose type is `T`. – Barry Jun 29 '17 at 21:34
  • @T.C AH, thank you... I knew there was a more straight forward way :) – George Jun 29 '17 at 21:44
  • @Bary... ok, that explains it, but forcing the type of the second parameter to go in a non deduceable context is, ahm, a bit less straight forward. I will edit the answer to include both and aprove it, if you don't mind – George Jun 29 '17 at 21:45
  • Apparently I can't edit your answers T.T, would you care to include the decltype signature options as well, in case people stumble upon this later ? – George Jun 29 '17 at 21:46
  • @George That's exactly the same thing that `decltype(a)` is doing. Please don't edit my answer. – Barry Jun 29 '17 at 21:47
  • 3
    Well... I cannot I won't, but, isn't decltype(a) a much much simpler way of doing this ? As in, it doesn't require understanding of deducible vs non-deducible context nor so much code. I mean I'm not saying the answer is wrong, only that the other one is equal valid and would be worth displaying side by side – George Jun 29 '17 at 21:53
  • 1
    @George I prefer being more explicit, I don't like using `decltype()` like this. It works just as well - it too creates a non-deduced context - so if you like it, by all means. – Barry Jun 29 '17 at 22:04
  • Are you sure that's what it does ? I assumed it just deduces the type of b to be the type of a... I mean, your example places the type of b in a non deduceable context and as such forces it to become the type of the template type that happens to be the tpye of a in this case. This is much more straight forward since it just takes the type of a and makes b of that type... at least so it seems to me, isn't that what is happening ? I'm not sure of the internals of decltype – George Jun 29 '17 at 22:15
  • 4
    @George Yes, I'm sure - for the 3rd time, `decltype(a)` is just as much a non-deduced context as `dont_deduce_t`, which is why I keep saying that they work the same way. You're forcing the type of `a` to be deduced and then just using that as the type of `b`. – Barry Jun 29 '17 at 23:01
  • @T.C. Does that create a non deduced context too? – David G Jun 30 '17 at 01:34
  • @0x499602D2 For the...4th(?) time, counting Barry's, yes, it does. – T.C. Jun 30 '17 at 19:27
  • Well... as far as I've recently observed decltype is actually inferior to your method since the type of a comes w/wo the const qualifier and the & or &&. Forcing some std::remove_reference/std::remove_const/std::add_const for it to be useful. Your method takes the template type only, so in that way it is much more correct. – George Jul 02 '17 at 12:39
  • @George can you provide examples? I can't find any difference in my tests. – Hedede Jul 03 '17 at 00:30
  • Try a func signature like: void f(T& t, const decltype(t)& m) than pass to it an m declared somewhere else with a const qualifier eg: const M m(); f(M{}, m) – George Jul 03 '17 at 07:06
8

There is answer in C++11: std::common_type http://en.cppreference.com/w/cpp/types/common_type

template<typename A>
void f_impl(A a, A b)
{

}

template<typename A, typename B>
void f(A a, B b)
{
    f_impl<typename std::common_type<A, B>::type>(a, b);
}


struct Z
{

};
struct W
{
    operator Z();
};

int main()
{
    f(1u, 1l); //work
    f(W{}, Z{});
    f(Z{}, W{}); //and this work too
}

https://godbolt.org/g/ieuHTS

Yankes
  • 1,958
  • 19
  • 20
  • Its a nice option, but still works through delegation not directly. But I can see it being useful in many other cases. – George Jun 30 '17 at 07:31
7

It's certainly possible, just with a little delegation. You've made the problem pretty easy by specifying that you always want the inferred type to be the type of the first argument, so all we need to do is drop a little hint to the compiler.

template <class T>
void do_stuff_impl(T a, T b) {
    cout << "Doing some work..." << endl;
}

template <class T, class S>
void do_stuff(T a, S b) {
    do_stuff_impl<T>(a, b);
}

Now the user can call do_stuff with any two arguments, and C++ will try to implicitly cast the second argument to match the type of the first. If the cast isn't valid, you'll get a template instantiation error. On GCC, it says cannot convert ‘b’ (type ‘A’) to type ‘B’, which is pretty accurate and to the point. And any compiler worth its salt is going to be able to inline that delegated call, so there should be negligible overhead.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • Whilst this accomplishes what I want I feel like its not exactly appropriate, since it still allows for calling the function with a different type for a and b (Which I don't want to allows if possible) – George Jun 29 '17 at 21:09
  • `do_stuff_impl>(a, b);` might be more appropriate. – Jarod42 Jun 29 '17 at 22:38
1

another way, seeking to express intent declaratively:

#include <type_traits>

// a B
struct B{};

// an A can be constructed from a B    
struct A{
    A() {};
    A(B) {};
};

// prove that A is constructible from B
static_assert(std::is_convertible<B, A>::value, "");

// enable this function only if a U is convertible to a T
template<
  // introduce the actors
  class T, class U,

  // declare intent    
  std::enable_if_t<std::is_convertible<U, T>::value>* = nullptr
>
void do_stuff(T, U)
{

}

int main()
{
    // legal
    do_stuff(A{}, B{});

    // does not compile
//    do_stuff(B{}, A{});

}

update:

to force the conversion, a lambda can be used:

// enable this function only if a U is convertible to a T
template<class T, class U,
std::enable_if_t<std::is_convertible<U, T>::value>* = nullptr
>
void do_stuff(T a, U b)
{
    return[](T& a, T b) -> decltype(auto)
    {

    }(a, b);
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • I'm sorry, but this does not solve my problem, maybe I was not good enough at expressing my intent here :/... in this example the type of U becomes B. The check do indeed assure me that the class CAN be converted to A, but I want it to be converted, not just the assurance that it can. Imagine that I had a method "do_more_stuff()" on A, if I took the second parameter (in this case of type U which becomes B) and called that method inside "do_stuff" it would generate a compilation error... so this is not good :/ – George Jun 29 '17 at 21:41
  • @George updated. added an inner lambda to actually perform the conversion. – Richard Hodges Jun 30 '17 at 09:02