7

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

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

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

在这种情况下,我总是想T从第一个参数中推断出来,但看起来我没有很好地表达这一点。2的类型是int,所以:

推断.cpp:5:5:错误:没有匹配函数调用“分配”
    赋值(&i, 2);
    ^~~~~~
推断.cpp:1:28:注意:候选模板被忽略:推断参数“T”的冲突类型(“双”与“整数”)
模板无效分配(T *a,T b){ *a = b; }

有没有办法可以声明assign()第二个参数不参与模板参数推导?

4

8 回答 8

13

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

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

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

于 2013-07-02T18:36:47.863 回答
4

问题是编译器从第一个和第二个参数中推断出冲突的信息。从第一个参数推导出Tdouble( iis a double);从第二个参数推导出T为( isint的类型)。2int

您在这里有两种主要可能性:

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

    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;
    }
    
于 2013-07-02T18:32:28.053 回答
3

只是将值2推导出来的类型int,与推导的模板参数不匹配&i。您需要将该值用作双精度:

assign(&i, 2.0);
于 2013-07-02T18:27:29.227 回答
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;
}

如果分配是不可能的,模板实例化将不会编译。

于 2013-07-02T18:32:23.160 回答
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);
}

第二个参数是任何可以转换为 a 的参数T,但我们通过通用引用来获取它并有条件地将其移入*dest. 我测试签名中的可转换性,而不是让正文无法编译,因为找不到重载失败似乎比无法编译正文更有礼貌。

活生生的例子

比较简单的:

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

以上节省了 1 move。如果您有一个昂贵的移动课程,或者一个仅复制且复制成本高的课程,这可以节省大量资金。

于 2013-07-03T04:07:00.230 回答
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);
}
于 2013-07-02T22:38:22.443 回答
0

显然std::identity不再存在(标准库中没有 std::identity 有什么原因吗?

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

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

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

这样,编译器会将整数输入参数转换为双精度以匹配函数模板,而无需扣除参数。

现场演示

于 2016-12-02T02:52:18.480 回答
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);
}

演示

于 2020-03-26T07:53:00.683 回答