s4y
s4y

Reputation: 51685

Use one argument for template parameter deduction?

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?

Upvotes: 7

Views: 2573

Answers (8)

Dino
Dino

Reputation: 342

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

Upvotes: 0

rmbianchi
rmbianchi

Reputation: 6521

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

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

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.

Upvotes: 1

A. K.
A. K.

Reputation: 38146

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);
}

Upvotes: 0

Vlad
Vlad

Reputation: 35594

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.

Upvotes: 2

Ben Voigt
Ben Voigt

Reputation: 283634

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.

Upvotes: 13

Andy Prowl
Andy Prowl

Reputation: 126432

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;
    }
    

Upvotes: 4

David G
David G

Reputation: 96810

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);

Upvotes: 3

Related Questions