【问题标题】:Using SFINAE to check function std::to_string exists for a type使用 SFINAE 检查函数 std::to_string 是否存在
【发布时间】:2019-05-29 05:43:51
【问题描述】:

我想创建一个函数模板,该函数模板专门用于可能应用了std::to_string 的类型,另一个用于我已为其定义了mylib::to_string 的类,并让任何可能不适用的类型其他实现或专业化。

例子:

// I only want this to be found if std::to_string exists for ItemType
template<typename ItemType>
void func(ItemType &i)
{
     std::cout << std::to_string(i);
}

// I only want this to be found if mylib::to_string exists for ItemType
template<typename ItemType>
void func(ItemType &i)
{
     std::cout << mylib::to_string(i);
}

// And finally, I'd like to be able to fall through to anything that has a << defined for streams
template<>
void func<MyClass>(MyClass &i)
{
     std::cout << MySpecialConverterFunc(i);
}

std::enable_if 语法总是有问题。如何将它添加到第一个模板中?

【问题讨论】:

  • 仅供参考,这么多天后编辑帖子并使现有答案无效,这在 SO 上是非常不受欢迎的。问一个新问题会更好。

标签: c++ templates sfinae


【解决方案1】:

对于 SFINAE,您不需要专门化,而是需要超载。无论如何,这比处理专业化(那些有怪癖)更明智。 SFINAE 是一种指导重载解析的机制,而不是用于选择函数模板的显式特化。

至于支票本身,你真的不需要enable_if。只需一点点 SFINAE 的表达即可:

template<typename ItemType>
auto func(ItemType &i) -> decltype(std::to_string(i), void())
{
     std::cout << std::to_string(i);
}

void func(MyClass &i)
{
     std::cout << MySpecialConverterFunc(i);
}

在上面的代码中,decltype 应用于逗号表达式。左侧是std::to_string(i),如果格式不正确,则整个表达式的格式不正确,我们会得到一个替换错误,使编译器丢弃该重载(“不是错误”部分)。如果格式正确,则右侧是 void() 文字,因此 decltype 按您的意图解析为 void

当替换失败时,我们只剩下第二个重载,所以它被选中了。无论如何都会为这种情况选择它,因为如果签名相同,非模板重载总是在重载决议中受到青睐。但添加检查以在其他情况下更好地指导编译器本身并不是一个坏主意。

【讨论】:

  • 我不明白如何使用它为定义了 std::to_string() 函数的所有类提供一个函数,并为定义了 MyStringFunct::to_string( ),还有另一个用于另一类对象。
  • @bpeikes - 然后你有另一个问题,这不是你最初在这里问的问题。尽管如此,我限制第一个重载的方式可以用来考虑另一个和第三个。如果您仍然不清楚,我建议您发一个新帖子,提出一个新问题。
  • 我没有意识到我的问题不清楚。这并不意味着通过更新答案来使答案无效。我只是想,在阅读了这些答案之后,我应该更详细地提出这个问题。请注意,我最初通过询问如何将 enable_if 添加到您的答案没有使用的第一个函数来非常具体。
  • @bpeikes - 是的,你很具体。你问的是你想到的解决方案,而不是问题。这并不意味着尽管如此,问题并不明显,并且其他解决方案也不是更好。
【解决方案2】:

避免 SFINAE 并使用 ADL 规则编写简单的代码

您可以通过利用 ADL(参数相关查找)来避免使用 SFINAE。 ADL 声明的是:

如果函数和类型都在同一个命名空间中声明,则在调用该函数时不必指定命名空间。

例如:

namespace foo 
{
    class MyClass {};
    std::string to_string(MyClass const& x) {
        return "[Element of MyClass]"; 
    } 
}

int main() {
    foo::MyClass x;

    std::cout << to_string(x); // This is legal because x is in namespace foo
}

如果您在命名空间中为您的类型编写 to_string 函数,那么您可以编写一次 func

template<class T>
void func(T const& t) {
    using std::to_string; 
    // This calls std::to_string if std::to_string is defined;
    // Otherwise it uses ADL to find the correct to_string function
    std::cout << to_string(t); 
}

在本例中,func 可以用 foo::MyClass 调用,因为在命名空间 foo 中有一个接受 MyClassto_string 函数:

int main() {
    func(10); // Prints 10; 
    func(3.1415); // Prints 3.1415

    foo::MyClass x;
    func(x); // Prints [Element of MyClass]
}

我不拥有的命名空间中的类型呢?

在这种情况下,我建议创建一个额外的命名空间并将 to_string 函数粘贴在那里,然后将其添加为 using 语句:

namespace textConvert {
    // Because we use std::to_string here, it automatically gets included
    // When we use textConvert::to_string
    using std::to_string; 

    // Convert a vector
    template<class T>
    std::string to_string(std::vector<T> const& vect) {
        using std::to_string; 

        std::string str = "[";        
        for(auto& elem : vect) {
            str += to_string(elem);
            str += ','; 
        }
        str.back() = ']';
        return str; 
    }
}

然后,我们可以更新func 以包含textConvert::to_string,并且由于textConvert 使用std::to_string 以及各种自定义转换函数,都包含在内!

template<class T>
void func(T const& t) {
    // This *also* automatically includes std::to_string
    // Because we put using std::to_string inside textConvert
    using textConvert::to_string;

    std::cout << to_string(t);
}

【讨论】:

    猜你喜欢
    • 2020-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多