SFINAE用于泛型类型的通用操作

时间:2017-10-16 14:34:54

标签: c++ templates c++17 sfinae enable-if

我有

  • 一个 Value 类,它可以用不同的类型构建( Foo,Bar,int,... )。

  • class Value 应该有一些常见的操作,例如<<,==,<,......在底层类型

  • 我在类定义之外添加了运算符<<

我必须使用以下代码:

#include <iostream>

struct Foo {
};

struct Bar {
};

struct Value {
    template<typename T>
    Value(T) {
    }
};

std::ostream &operator<<(std::ostream &os, const Bar &) {
    return os << "Bar\n";
}

std::ostream &operator<<(std::ostream &os, const Value &value) {

    auto visitor = [&](auto a) -> decltype(os << a) {
        return os << a;
    };

    // Works
    visitor(Bar{});

    // Infinity call of this function with Value.
    visitor(Foo{});

    return os;
}

int main() {
    std::cout << Value(1);

    return 0;
}

Live example.

问题:如果基础类型没有实现运算符&lt;&lt;,则运算符&lt;&lt;从获得称为递归无穷大。我想得到一个编译器错误,如不匹配调用操作符&lt;&lt;(std:ostream&amp;,const Value&amp;)... 将SFINAE用于我的访问者模式(此处未显示)。< / p>

我需要的是:

[&](auto a) -> std::enable_if_t<addressof?(os << a) != addressof?(os << Value{})>::value> {
    return os << a;
};

如果函数相同,则禁用此lambda。这可能吗?

不是有价值的解决方案:

  • 使值明确
  • 使用Foo
  • 禁用Value的构造

3 个答案:

答案 0 :(得分:4)

您可以添加一个包装器以强制只进行一次转换:

template <typename T>
struct OneConversion
{
    OneConversion(const T& t) : t(t) {}

    operator const T&() const {return t;}

    const T& t;  
};

template <typename T>
struct isOneConversion : std::false_type {};

template <typename T>
struct isOneConversion<OneConversion<T>> : std::true_type {};

struct Value {
    template<typename T, std::enable_if_t<!isOneConversion<T>::value>* = nullptr>
    Value(T) {}
};

std::ostream &operator<<(std::ostream &os, const Value &value) {

    auto visitor = [&](auto a) -> decltype(os << OneConversion<decltype(a)>(a)) {
        return os << OneConversion<decltype(a)>(a);
    };

    // Works
    visitor(Bar{});

    visitor(Foo{}); // Error as expected.

    return os;
}

documentation

答案 1 :(得分:2)

你可以替换

std::ostream &operator<<(std::ostream &os, const Value &value)
{
    // ...
}

template <typename T, typename = std::enable_if_t<std::is_same_v<T, Value>>>
std::ostream &operator<<(std::ostream &os, const T &value)
{
    // ...
}

您仍然可以使用它打印Value个对象,但不需要隐式转换。

将其放入您的代码会导致visitor(Foo{});失败并出现以下错误,这似乎就是您想要的。

...
main.cpp:29:12: error: no match for call to '(operator<<(std::ostream&, const T&) [with T = Value; <template-parameter-1-2> = void; std::ostream = std::basic_ostream<char>]::<lambda(auto:1)>) (Foo)'
 visitor(Foo{});
 ~~~~~~~^~~~~~~
...

答案 2 :(得分:2)

如果不修改std::ostream &operator<<(std::ostream &os, const Value &value)的签名,我们可以检查是否尝试调用operator<<a在我们的lambda中推导出的类型是否格式良好:

auto visitor = [&](auto a) -> decltype(
                              static_cast<std::ostream&(*)(std::ostream&, const decltype(a)&)>(&operator<<)
                             (os, a)
                             )
{
    return os << a;
};

Bar一起使用时,Foo失败并显示错误消息:

error: invalid static_cast from type '<unresolved overloaded function type>' to type 'std::ostream& (*)(std::ostream&, const Foo&

Demo