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

Use one argument for template parameter deduction?

本文关键字:参数 一个      更新时间:2023-10-16

假设我有一个模板函数,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"的冲突类型("双精度"与"整数"(template void assign(T *a, T b( { *a = b; }

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

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

template<typename T>
void assign( T* a, typename std::identity<T>::type b );
  • 演示:http://ideone.com/ZW6Mpu

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

问题是编译器从第一个和第二个参数中推断出相互冲突的信息。从第一个参数,它推导出Tdouble(i是一个double);从第二个参数,它推导出Tint(2的类型是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; }
    

    如果当函数不可转换为T时,您不需要从重载集中排除U,您可能希望在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的值推导为类型int,这与&i推导的模板参数不匹配。您需要将该值用作双精度值:

assign(&i, 2.0);

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

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

如果无法进行赋值,则不会编译模板实例化。

我的尝试看起来像这样:

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中。 我在签名中测试可转换性,而不是让正文无法编译,因为找不到重载失败似乎比编译正文更礼貌。

活生生的例子。

与更简单的相比:

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

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

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

演示

或者

,可以使用decltype将第二个参数类型转换为第一个参数的类型。

template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
    double i;
    assign(&i, (decltype(i))2);
}

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

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

template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
  double i;
  assign<double>(&i, 2);
}

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

现场演示

相关文章: