【问题标题】:What are the rules for choosing between a variadic template method and a usual template method?在可变参数模板方法和常用模板方法之间进行选择的规则是什么?
【发布时间】:2015-02-03 20:58:12
【问题描述】:

我在课堂上有两个模板运算符:

    template<class T>
    size_t operator()(const T& t) const {
        static_assert(boost::is_pod<T>(), "Not a POD type");
        return sizeof t;
    }

    template<typename... T>
    size_t operator()(const boost::variant<T...>& t) const
    {
        return boost::apply_visitor(boost::bind(*this, _1), t);
    }

我将boost::variant&lt;some, pod, types, here&gt; 作为参数传递给这些运算符。 GCC 4.8 和 llvm 6.0 可以很好地编译代码,选择 boost::variant 参数化运算符。 gcc 4.7 选择了const T&amp; t 参数化运算符,因此由于静态断言而无法编译。

那么,我有一个问题,在这两者之间进行选择的规则是什么? 我认为 gcc 4.7 肯定有 bug,但我没有任何证据。

【问题讨论】:

    标签: c++ templates gcc boost


    【解决方案1】:

    关键部分在[temp.deduct.partial]:

    使用两组类型来确定偏序。对于涉及的每个模板,都有 原始函数类型和转换后的函数类型。 [注:转换类型的创建 在 14.5.6.2 中描述。 --end note ] 推演过程使用转换后的类型作为参数 模板和另一个模板的原始类型作为参数模板。这个过程做了两次 对于偏序比较中涉及的每种类型:一次使用转换后的 template-1 作为 参数模板和模板 2 作为参数模板,并再次使用转换后的模板 2 作为参数模板,template-1 作为参数模板。

    这真的很密集,即使对于 C++ 标准,但它的基本意思是这个。以我们的两个重载为例:

    template <class T> // #1
    size_t operator()(const T& t) const
    
    template <typename... T> // #2
    size_t operator()(const boost::variant<T...>& t)
    

    我们将基本上为每一个分配一些独特的类型,并尝试查看另一个是否适用。因此,让我们为#1 选择一些类型A,为#2 选择B,C,Doperator()(const A&amp;) 是否适用于 #2?不,operator()(const boost::variant&lt;B,C,D&gt;&amp;) 是否适用于 #1?是的。因此,部分排序规则表明#2#1 更专业。

    所以,从 [temp.func.order]:

    推论过程确定一个模板是否比另一个更专业。如果 因此,更专业的模板是偏排序过程选择的模板。

    而且来自 [over.match.best]:

    [A] 可行函数F1 被定义为比另一个可行函数更好的函数 F2如果
    — [..]
    F1F2 是函数模板特化,F1 的函数模板更特化 根据 14.5.6.2 中描述的部分排序规则,而不是 F2 的模板。

    因此,#2 在任何适用的情况下都应选择。如果 GCC 选择了#1,那就是不合格的行为并且是一个错误。

    【讨论】:

    • 解释得很好!谢谢
    【解决方案2】:

    一般来说,编译器只是将所有推导的模板实例视为潜在的重载,选择“最佳可行函数”(第 13.3.3 节)。

    这确实意味着 GCC 4.7 有一个错误。

    参见 §14.8.3:重载分辨率

    描述所有模板实例将作为任何非模板声明的重载加入候选集:

    一个函数模板可以被它的(非模板)函数重载 名称或(其他)同名的功能模板。当打电话给那个 写入名称(显式或隐式使用运算符符号), 模板参数推导(14.8.2)和任何显式模板的检查 为每个函数模板执行参数(14.3)以找到模板 可与该函数模板一起使用的参数值(如果有) 实例化一个可以用 调用参数。对于每个函数模板,如果参数推导和 检查成功,使用模板参数(推导和/或显式) 合成单个函数模板特化的声明 添加到要在重载中使用的候选函数集 分辨率。如果对于给定的函数模板,参数推导失败,则否 这样的函数被添加到该模板的候选函数集中。 该 完整的候选函数集包括所有综合声明 以及所有同名的非模板重载函数。 该 综合声明在其余部分中被视为任何其他函数 重载决议,除非在 13.3.3 中明确指出。

    就您的问题而言,重载最终无法区分(信用:@Piotr S)。在这种情况下,应用“部分排序”(第 14.5.6.2 节):

    F1和F2是函数模板特化,F1的函数模板比​​F2的模板更特化

    请注意,事情可能会变得非常棘手,例如“开放模板”版本采用 T&amp; 而不是 T const&amp;(首选非 const 引用,其他条件相同)。

    当您有多个重载最终具有相同的重载解析“等级”时,调用格式错误,编译器将诊断出不明确的函数调用。

    【讨论】:

    • 它不像对转化进行排名那样挑选最佳匹配。偏序使得后者重载成为 boost::variant 参数的首选。
    • @PiotrS。我认为您的回答与引用的部分背道而驰。我也不确定 PO 是否是在同一类中声明的重载的问题。 (这个技巧通常用于在命名空间范围内不起作用的递归/可变分派)
    • 引用部分的反面是什么?在实例化两个函数模板后,它们彼此无法区分,因此认为 PO:“F1 和 F2 是函数模板特化,根据所述的偏序规则,F1 的函数模板比​​ F2 的模板更特化在 14.5.6.2 中。”
    • 哦。哎呀。我想到的“最佳匹配”变成了确切地称为部分排序。我会修改措辞!
    • 注意more specialized在这里有非常具体的含义。在这种情况下,模板特化并不比非特化更特化!
    猜你喜欢
    • 1970-01-01
    • 2021-02-11
    • 2013-10-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多