7

Let’s say I have a template function, assign(). It takes a pointer and a value and assigns the value to the pointer’s target:

template <typename T> void assign(T *a, T b) { *a = b; }

int main() {
    double i;
    assign(&i, 2);
}

In this case I always want T to be deduced from the first argument, but it looks like I didn’t do a good job of expressing this. 2’s type is int, so:

deduce.cpp:5:5: error: no matching function for call to 'assign'
    assign(&i, 2);
    ^~~~~~
deduce.cpp:1:28: note: candidate template ignored: deduced conflicting types for parameter 'T' ('double' vs. 'int')
template  void assign(T *a, T b) { *a = b; }

Is there a way I can declare assign() so that the second argument doesn’t participate in template parameter deduction?

s4y
  • 50,525
  • 12
  • 70
  • 98
  • So, one problem with the above is inefficiency. Suppose `T` is `std::vector`. The argument `b` is taken by-value, then copied (not moved) into `a`. A small improvement might be changing the implementation of `assign` to `*a = std::move(b)`, which for primitive types costs nothing, and for complex types could save a lot. A large improvement would be to perfect forward `b`. – Yakk - Adam Nevraumont Jul 03 '13 at 04:00
  • @Yakk Totally agreed — I just wrote it as an example of a function that takes a pointer to and value of the same type. In actuality it only takes primitives and is more useful than this guy :). – s4y Jul 03 '13 at 17:50

8 Answers8

13

Using two type parameters is probably the best option, but if you really want to perform deduction only from the first argument, simply make the second non-deducible:

template<typename T>
void assign( T* a, typename std::identity<T>::type b );

An earlier version of this answer suggested using the template alias feature introduced in C++11. But template aliases are still a deducible context. The primary reason that std::identity and std::remove_reference prevents deduction is that template classes can be specialized, so even if you have a typedef of a template type parameter, it's possible that another specialization has a typedef of the same type. Because of the possible ambiguity, deduction doesn't take place. But template aliases preclude specialization, and so deduction still occurs.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • +1. Given the edit to the question (which I missed), this seems to be the right answer. – Andy Prowl Jul 02 '13 at 18:43
  • This is a really cool answer but it doesn’t work for me. I get the same “conflicting types” error from my compiler: ideone.com/GIJbj1 – s4y Jul 02 '13 at 19:19
  • Looks like `std::remove_reference` is equivalent (if a little less readable), too. – s4y Jul 02 '13 at 19:21
  • @Sidnicious: My bad, looks like template aliases are still deducible :( – Ben Voigt Jul 02 '13 at 19:51
  • There is no `std::identity`, is there? – aschepler Jul 02 '13 at 20:44
  • @aschepler: [g++ thinks so](http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-api-4.5/a00497.html). But see the ideone link for a trivial reimplementation not using any standard headers. – Ben Voigt Jul 02 '13 at 20:52
  • @aschepler you appear to be right, it was suggested but then removed from C++11: https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/vrrtKvA7cqo – Mark Ransom Jul 02 '13 at 21:38
4

The problem is that the compiler is deducing conflicting information from the first and the second argument. From the first argument, it deduces T to be double (i is a double); from the second one, it deduces T to be int (the type of 2 is int).

You have two main possibilities here:

  • Always be explicit about the type of your arguments:

    assign(&i, 2.0);
    //         ^^^^
    
  • Or let your function template accept two template parameters:

    template <typename T, typename U> 
    void assign(T *a, U b) { *a = b; }
    

    In this case, you may want to SFINAE-constraint the template so that it does not partecipate to overload resolution in case U is not convertible to T:

    #include <type_traits>
    
    template <typename T, typename U,
        typename std::enable_if<
            std::is_convertible<U, T>::value>::type* = nullptr>
    void assign(T *a, U b) { *a = b; }
    

    If you do not need to exclude your function from the overload set when U is not convertible to T, you may want to have a static assertion inside assign() to produce a nicer compilation error:

    #include <type_traits>
    
    template<typename T, typename U>
    void assign(T *a, U b)
    {
        static_assert(std::is_convertible<T, U>::value,
            "Error: Source type not convertible to destination type.");
    
        *a = b;
    }
    
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Or `static_assert` within the function, since you don't have another overload that is viable in the non-convertible case. – Praetorian Jul 02 '13 at 18:33
  • @Praetorian: Or that, yes. I wasn't thinking of it because the function is small and the error message would be understandable anyway, but you're right, it's an option – Andy Prowl Jul 02 '13 at 18:34
  • Sorry about the late edit — this is a pretty legit answer. Upvoted, at least. – s4y Jul 02 '13 at 18:48
3

It's just that the value 2 is deduced to the type int, which doesn't match the template parameter deduced by &i. You need to use the value as a double:

assign(&i, 2.0);
David G
  • 94,763
  • 41
  • 167
  • 253
  • I know. That works but it’s brittle and ugly — if `i` is an `unsigned long` then my second argument has to be `2ul`, if it’s a float it has to be `(float)2`, etc. In the real code, it takes a few arguments and gets nasty fast. – s4y Jul 02 '13 at 18:33
  • @Sidnicious Then I guess you might want to go with Andy's answer. – David G Jul 02 '13 at 18:36
2

Why not just use two independent parameter types, one for the source and one for the destination?

template <typename D, typename S> void assign(D *a, S b) { *a = b; }

int main(int argc, char* argv[])
{
    double i;
    assign(&i, 2);
    return 0;
}

If the assignment is not possible, the template instantiation won't compile.

Vlad
  • 35,022
  • 6
  • 77
  • 199
1

My attempt would look something like this:

template<typename T, typename U>
typename std::enable_if< std::is_convertible< U&&, T >::value >::type // not quite perfect
assign( T* dest, U&& src ) {
  *dest = std::forward<U>(src);
}

the second argument is anything you can convert to a T, but we take it by universal reference and conditionally move it into *dest. I test for convertability in the signature rather than have the body fail to compile, because failure-to-find-an-overload seems more polite than failing to compile-the-body.

Live example.

Compared to the simpler:

template<typename T>
void assign( T* dest, typename std::identity<T>::type src ) {
  *dest = std::move(src);
}

the above saves 1 move. If you have an expensive to move class, or a class that is copy-only and expensive to copy, this could save a significant amount.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

Alternatively, you can use decltype to typecast the second argument to be the type of first.

template <typename T> void assign(T *a, T b) { *a = b; }

int main() {
    double i;
    assign(&i, (decltype(i))2);
}
A. K.
  • 34,395
  • 15
  • 52
  • 89
  • Yeah, but you have to remember to do that every time you call it. It gets ugly fast — I posted this question because was I was trying to improve a function took several numbers as arguments. Casts everywhere! – s4y Jul 03 '13 at 17:55
0

Apparently std::identity is not there anymore (Is there a reason why there is not std::identity in the standard library?)

But you can specify the parameter type in the parameter type list, when calling the function:

template <typename T> void assign(T *a, T b) { *a = b; }

int main() {
  double i;
  assign<double>(&i, 2);
}

In this way the compiler will convert the integer input argument to double to match the function template, without argument deduction.

Live demo

Community
  • 1
  • 1
rmbianchi
  • 6,241
  • 6
  • 26
  • 27
0

C++20 has std::type_identity which can be used to establish a non-deduced context:

#include <type_traits>

template <typename T>
void assign(T *a, std::type_identity_t<T> b) {
    *a = b;
}

int main() {
    double i;
    assign(&i, 2);
}

Demo

Dino
  • 342
  • 1
  • 9