使用一个参数进行模板参数推导?

时间:2013-07-02 18:25:52

标签: c++ templates template-deduction

假设我有一个模板函数assign()。它接受一个指针和一个值,并将值赋给指针的目标:

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

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

在这种情况下,我总是希望从第一个参数中推导出T,但看起来我没有做好表达这一点的工作。 2的类型为int,因此:

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

有没有办法可以声明assign(),以便第二个参数不参与模板参数扣除?

8 个答案:

答案 0 :(得分:13)

使用两个类型参数可能是最好的选择,但是如果你真的只想从第一个参数执行推论,只需要使第二个不可推导:

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

此答案的早期版本建议使用C ++ 11中引入的模板别名功能。但模板别名仍然是一种可推导的背景。 std::identitystd::remove_reference阻止演绎的主要原因是模板类可以是专用的,因此即使你有一个模板类型参数的typedef,另一个专业化也可能有一个typedef相同类型。由于可能存在歧义,因此不会进行演绎。但模板别名排除了专业化,因此仍然会进行演绎。

答案 1 :(得分:4)

问题在于编译器正在从第一个和第二个参数中推断出冲突的信息。从第一个参数开始,它将T推导为doubleidouble);从第二个参数,它推导出Tint2的类型是int)。

这里有两个主要的可能性:

  • 始终明确您的参数类型:

    assign(&i, 2.0);
    //         ^^^^
    
  • 或者让您的功能模板接受两个模板参数:

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

    在这种情况下,您可能希望对模板进行SFINAE约束,以便在U无法转换为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; }
    

    如果您不需要在U无法转换为T时从过载集中排除您的函数,您可能希望在assign()中有一个静态断言以产生更好的效果编译错误:

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

答案 2 :(得分:3)

只是将值2推断为类型int,这与&i推导出的模板参数不匹配。您需要将该值用作double:

assign(&i, 2.0);

答案 3 :(得分:2)

为什么不使用两个独立参数类型,一个用于源,一个用于目标?

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

如果无法进行分配,则模板实例化将无法编译。

答案 4 :(得分:1)

我的尝试看起来像这样:

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

第二个参数是您可以转换为T的任何内容,但我们通过通用引用将其视为有条件地将其移至*dest。我在签名中测试可转换性而不是让主体无法编译,因为找不到重载似乎比没有编译主体更有礼貌。

Live example

与简单相比:

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

以上节省1 move。如果你有一个昂贵的移动课程,或者一个只复制且复制费用昂贵的课程,这可以节省很多。

答案 5 :(得分:0)

或者,您可以使用decltype将第二个参数类型化为第一个参数。

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

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

答案 6 :(得分:0)

显然std::identity不再存在(Is there a reason why there is not std::identity in the standard library?

但是,在调用函数时,可以在参数类型列表中指定参数类型:

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

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

通过这种方式,编译器会将整数输入参数转换为double以匹配函数模板,而不会产生参数推断。

Live demo

答案 7 :(得分:0)

C ++ 20具有std::type_identity,可用于建立非推论上下文:

#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

相关问题