使用SFINAE根据函数的特定过载是否存在来选择函数

Kyl*_*yes 14 c++ c++11

我一直在尝试根据是否operator<<(std::ostream&, const T&)存在过载来在两个模板化函数之间进行选择.

例:

template <typename T, typename std::enable_if</* ? */, int>::type = 0>
std::string stringify(const T& t)
{
    std::stringstream ss;
    ss << t;
    return ss.str();
}

template <typename T, typename std::enable_if</* ? */, int>::type = 0>
std::string stringify(const T& t)
{
    return "No overload of operator<<";
}

struct Foo { };

int main()
{
    std::cout << stringify(11) << std::endl;
    std::cout << stringify(Foo{}) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

这可能吗?如果是这样,你会如何解决这个问题?

Pra*_*ian 18

当存在时,不需要enable_if使用表达式SFINAE来选择正确的过载operator<<.

namespace detail
{
    template<typename T>
    auto stringify(std::stringstream& ss, T const& t, bool)
        -> decltype(ss << t, void(), std::string{})
    {
        ss << t;
        return ss.str();
    }

    template<typename T>
    auto stringify(std::stringstream&, T const&, int)
        -> std::string
    {
        return "No overload of operator<<";
    }
}

template <typename T>
std::string stringify(const T& t)
{
    std::stringstream ss;
    return detail::stringify(ss, t, true);
}
Run Code Online (Sandbox Code Playgroud)

现场演示

stringify函数模板只是代表们的一个detail::stringify函数模板.然后,如果表达式ss << t格式正确,则选择第一个.未命名的bool参数用于两个detail::stringify实现之间的消歧.由于主stringify函数true作为参数传递detail::stringify,因此当operator<<存在重载时,第一个函数将是更好的匹配.否则将选择第二个.

decltype(ss << t, void(), std::string{})第一个stringify模板的尾随返回类型中的此表达式可能需要更详细的解释.这里我们有一个表达式,由3个由逗号运算符分隔的子表达式组成.

第一个ss << t是确定该函数模板是否通过模板参数替换并将被添加到重载决策集的内容.如果表达式格式正确,即如果所讨论的类型超载,则会发生这种情况operator<<.

中间子表达式void()除了确保operator,未选择某些用户定义之外不执行任何操作(因为您不能operator,使用void参数类型重载).

第三个也是最右边的子表达式std::string{}决定了detail::stringify函数的返回类型.

  • @Adam:它没有两个参数; 它有一个参数,由一个带逗号运算符的表达式组成.如果`ss << t`格式正确,那么完整表达式的类型是`std :: string`.如果不是,则替换失败并且模板未实例化,因此选择第二个版本. (5认同)
  • @Adam - 替换发生在函数类型中使用的类型/表达式(即其返回类型和参数类型)及其模板参数中.因此,虽然`ss << t`不一定需要在`decltype`中出现,但是正文中的`ss << t`将不会起到同样的作用,因为那时替换会成功并导致硬编译错误而不是SFINAE出于过载集的函数. (2认同)