문제

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?

도움이 되었습니까?

해결책 6

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

다른 팁

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.

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

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

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.

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.

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

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

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top