Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 906
s4y Avatar asked Jul 02 '13 18:07

s4y


2 Answers

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 );
  • Demo: http://ideone.com/ZW6Mpu

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.

like image 198
Ben Voigt Avatar answered Oct 05 '22 05:10

Ben Voigt


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;
    }
    
like image 36
Andy Prowl Avatar answered Oct 05 '22 03:10

Andy Prowl