【问题标题】:What's the right way to fix this template resolution ambiguity?解决此模板分辨率歧义的正确方法是什么?
【发布时间】:2016-07-13 07:09:58
【问题描述】:

假设我写了:

template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
void foo() { std::cout << "T is integral." << std::endl; }

template <typename T>
void foo() { std::cout << "Any T." << std::endl; }

int main() { foo<short>(); }

当我编译这个时,我得到一个关于调用歧义的错误(如果我用float 替换short 则没有错误)。我应该如何修复此代码,以便获得整数类型的较高版本和较低版本?

如果您的建议适用于除通用版本之外的多个专用版本的foo(),则可获得奖励积分。

【问题讨论】:

  • std::enable_if_t 是 c++14 顺便说一句。
  • 当你被正确的问题解决方案淹没时,这一定是个好日子 :-)
  • 这里有很好的解释:stackoverflow.com/questions/31500426/…
  • @RichardHodges:凭借经验,您设法增加了提出广受欢迎的问题的机会。不幸的是,通常是没有经验的人更需要这个。唉。
  • 我没有时间为这个想法写一个完整的答案,但是到目前为止,一个与所有答案不同的想法是使用元编程库,如 boost::mpl 或 loki 和构建一个编译时关联容器,将类型特征与仿函数相关联,并在编译时使用该数据结构查找正确的函数。您可以轻松地将其扩展为适用于大量可能的特征组合,它使特征和功能之间的关联变得清晰。

标签: c++ c++11 c++14 sfinae enable-if


【解决方案1】:

我喜欢Xeo's approach 解决这个问题。让我们做一些带有后备的标签调度。创建一个从自身向下继承的选择器结构:

template <int I>
struct choice : choice<I + 1> { };

template <> struct choice<10> { }; // just stop somewhere

所以choice&lt;x&gt; 可以转换为choice&lt;y&gt;x &lt; y,这意味着choice&lt;0&gt; 是最好的选择。现在,您需要最后一个案例:

struct otherwise{ otherwise(...) { } };

有了这个机制,我们可以用一个额外的参数来转发我们的主函数模板:

template <class T> void foo() { foo_impl<T>(choice<0>{}); }

然后让你的 top 选择变得完整,你的最坏情况的选择......任何东西:

template <class T, class = std::enable_if_t<std::is_integral<T>::value>>
void foo_impl(choice<0> ) {
    std::cout << "T is integral." << std::endl;
}

template <typename T>
void foo_impl(otherwise ) {
    std::cout << "Any T." << std::endl;
}

这使得在中间添加更多选项变得非常容易。只需为choice&lt;1&gt;choice&lt;2&gt; 或其他任何内容添加重载。也不需要不相交的条件。 choice&lt;x&gt; 的优先重载解决方案可以解决这个问题。

如果你另外传入T 作为参数会更好,因为重载比专门化要好得多:

template <class T> struct tag {};
template <class T> void foo() { foo_impl(tag<T>{}, choice<0>{}); }

然后你就可以疯狂了:

// special 1st choice for just int
void foo_impl(tag<int>, choice<0> );

// backup 1st choice for any integral
template <class T, class = std::enable_if_t<std::is_integral<T>::value>>
void foo_impl(tag<T>, choice<0> );

// 2nd option for floats
template <class T, class = std::enable_if_t<std::is_floating_point<T>::value>>
void foo_impl(tag<T>, choice<1> );

// 3rd option for some other type trait
template <class T, class = std::enable_if_t<whatever<T>::value>>
void foo_impl(tag<T>, choice<2> );

// fallback 
template <class T>
void foo_impl(tag<T>, otherwise );

【讨论】:

  • 我不明白你需要tag。新参数似乎没有做任何事情。第一个例子也是重载foo_impl(没有专门化)。附:你在最后一个后备中错过了一个逗号。
  • @JDługosz 你看到我对int 做了什么吗? tag 允许我添加这样的重载。
  • 在阅读了链接的文章后,我明白了你在做什么。选择结构提供有序重载,其他参数(如果 yiu 有的话)将基于 T 并完全匹配。因此,如果其他参数全部相同,则选择提供所有启用的参数的排名。酷!
  • 啊,tag 可以轻松添加精确的单一类型匹配,而无需使用 enable-if。在这种情况下,该函数无论如何都不是模板,因此它更简单(以将参数添加到所有其他参数为代价)。假设 c-v、ref 等转换不会搞砸。
  • 看起来这个机器属于某些模板/类型特征实用程序库。
【解决方案2】:

使用标签调度 (C++11) 的另一个选项:

#include <iostream>

void foo_impl(std::false_type) {
    std::cout << "Any T." << std::endl;
}

void foo_impl(std::true_type) {
    std::cout << "T is integral." << std::endl;
}

template <typename T>
void foo() {
    foo_impl(std::is_integral<typename std::remove_reference<T>::type>());
  //foo_impl(std::is_integral<typename std::remove_reference_t<T>>()); // C++14
}

int main() { 
    foo<short>();  // --> T is integral.
    foo<short&>(); // --> T is integral.
    foo<float>();  // --> Any T.
}

借自 Scott Meyers Effective Modern C++ item 27.

【讨论】:

    【解决方案3】:

    一种方式:

    template <typename T, typename std::enable_if_t<std::is_integral<T>::value>* = nullptr>
    void foo() { std::cout << "T is integral." << std::endl; }
    
    template <typename T, typename std::enable_if_t<not std::is_integral<T>::value>* = nullptr>
    void foo() { std::cout << "Any T." << std::endl; }
    

    另一种方法是遵循模板函数对象:

    template<class T, typename = void>
    struct foo_impl
    {
        void operator()() const {
            std::cout << "Any T." << std::endl;
        }
    };
    
    template<class T>
    struct foo_impl<T, std::enable_if_t<std::is_integral<T>::value>>
    {
        void operator()() const {
            std::cout << "T is integral." << std::endl;
        }
    };
    
    template<class T>
    void foo() {
        return foo_impl<T>()();
    }
    

    【讨论】:

    • 类包装器真的有必要避免通用版本和特定于整数的版本的“不相交条件”吗?
    • 推迟上课可以让我将部分专业化带入聚会。我发现它更灵活。
    • 但是直接回答你的问题,如果你想避免在不相交的条件下写作,它要么推迟到一个类,要么推迟到标签调度。
    【解决方案4】:

    一种方法是:

    template <typename T>
    std::enable_if_t<std::is_integral<T>::value, void> foo () {
        std::cout << "integral version" << std::endl;
    }
    
    template <typename T>
    std::enable_if_t<!std::is_integral<T>::value, void> foo () {
        std::cout << "general version" << std::endl;
    }
    

    有用法:

    foo<int> ();
    foo<double> ();
    struct X {};
    foo<X> ();
    

    输出是:

    integral version
    general version
    general version
    

    【讨论】:

    • 与@user2807083 的注释相同:这可行,但是您的代码适用于任何以 T 不是整数为条件的 T,这有点令人困惑。此外,也不能很好地扩展,因为明天有人会想要另一个专门用于大小至少为 7 的非整数类型;然后是另一个专业;等等等等,每次你需要在通用版本上限定条件。
    【解决方案5】:

    AFAIK,sfinae 适用于函数参数,所以尝试添加具有默认值的依赖类型的参数

    template <typename T>
    void foo(typename std::enable_if_t<std::is_integral<T>::value>* = 0) 
    { std::cout << "T is integral." << std::endl; }
    
    template <typename T>
    void foo(typename std::enable_if_t<!std::is_integral<T>::value>* = 0) 
    { std::cout << "Any T." << std::endl; }
    

    【讨论】:

    • 这行得通,但是你有适用于任何 T 条件的 T 不是整数的代码,这有点令人困惑。此外,也不能很好地扩展,因为明天有人会想要另一个专门用于大小至少为 7 的非整数类型;然后是另一个专业;等等等等,每次你需要在通用版本上限定条件。
    • 嗯,比你有@Barry 的答案,据我所知,这是可扩展的。
    猜你喜欢
    • 2013-10-27
    • 1970-01-01
    • 2019-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-10
    • 2010-10-10
    相关资源
    最近更新 更多