【问题标题】:Use one argument for template parameter deduction?使用一个参数进行模板参数推导?
【发布时间】:2013-06-30 06:18:46
【问题描述】:

假设我有一个模板函数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'
    赋值(&i, 2);
    ^~~~~~
推断.cpp:1:28:注意:候选模板被忽略:推断参数“T”的冲突类型(“双”与“整数”)
模板无效分配(T *a,T b){ *a = b; }

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

【问题讨论】:

  • 所以,上面的一个问题是效率低下。假设Tstd::vector。参数b 按值获取,然后复制(未移动)到a。一个小的改进可能是将assign 的实现更改为*a = std::move(b),这对于原始类型来说没有任何成本,而对于复杂类型来说可以节省很多。一个很大的改进是完善转发b
  • @Yakk 完全同意——我只是把它写成一个函数的例子,它接受一个指针和相同类型的值。实际上它只需要原语并且比这家伙更有用:)。

标签: c++ templates template-argument-deduction


【解决方案1】:

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

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

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

【讨论】:

  • +1。鉴于对问题的编辑(我错过了),这似乎是正确的答案。
  • 这是一个非常酷的答案,但它对我不起作用。我从编译器中得到相同的“冲突类型”错误:ideone.com/GIJbj1
  • 看起来 std::remove_reference 也是等价的(如果可读性差一点的话)。
  • @Sidncious:我的错,看起来模板别名仍然可以推断:(
  • 没有std::identity,有吗?
【解决方案2】:

问题在于编译器从第一个和第二个参数中推断出冲突的信息。从第一个参数推导出Tdoubleidouble);从第二个参数推导出Tint2 的类型为@987654328 @)。

你有两种主要的可能性:

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

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

【讨论】:

  • 或函数内的static_assert,因为您没有另一个在不可转换情况下可行的重载。
  • @Praetorian:或者那个,是的。我没有想到它,因为函数很小,而且错误消息无论如何都可以理解,但你是对的,这是一个选项
  • 抱歉,编辑晚了——这是一个非常合理的答案。至少赞成。
  • @Sidncious:没问题:)
【解决方案3】:

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

assign(&i, 2.0);

【讨论】:

  • 我知道。这行得通,但它又脆又丑——如果iunsigned long,那么我的第二个参数必须是2ul,如果它是一个浮点数,它必须是(float)2,等等。在实际代码中,它需要一个争论很少,很快就会变得讨厌。
  • @Sidncious 那么我猜你可能想接受安迪的回答。
【解决方案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;
}

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

【讨论】:

  • 奇数使用D 作为源,S 作为目标
  • @Ben:确实 :) 刚刚交换了它们。
  • 如果你要这样做,为什么不完美地转发S
【解决方案5】:

我的尝试看起来像这样:

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。如果您有一个昂贵的移动课程,或者一个仅复制且复制成本高的课程,这可以节省大量费用。

【讨论】:

    【解决方案6】:

    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

    【讨论】:

      【解决方案7】:

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

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

      【讨论】:

      • 是的,但你必须记住每次调用它时都要这样做。它很快就变得丑陋了——我发布了这个问题是因为我试图改进一个将多个数字作为参数的函数。无处不在!
      【解决方案8】:

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

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

      Live demo

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-16
        • 2018-07-04
        • 1970-01-01
        相关资源
        最近更新 更多