首页 文章

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

提问于
浏览
7

假设我有一个模板函数, 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() ,以便第二个参数不参与模板参数推导?

7 回答

  • 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

  • 0

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

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

    此答案的早期版本建议使用C 11中引入的模板别名功能 . 但模板别名仍然是可推断的上下文 . std::identitystd::remove_reference 阻止演绎的主要原因是模板类可以是专用的,因此即使你有一个模板类型参数的typedef,它也会发生 . {2971196_ t . 但模板别名排除了特殊化,因此仍然会发生演绎 .

  • 13

    问题是编译器正在从第一个和第二个参数中推断出冲突的信息 . 从第一个参数,它推导 Tdoubleidouble) ;从第二个参数,它推断 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;
    }
    
  • 1

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

    assign(&i, 2.0);
    
  • 4

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

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

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

  • 2

    我的尝试看起来像这样:

    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 . 如果你有一个昂贵的移动类,或一个只复制和复制昂贵的类,这可以节省大量的 .

  • 3

    或者,您可以使用 decltype 将第二个参数类型转换为第一个参数 .

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

相关问题