SFINAE 用于泛型类型的泛型操作
SFINAE for generic operation on generic type
我有
a class Value 可以用不同的类型构造 (Foo, Bar, int,...).
class Value 应该有常见的操作,比如 <<, ==, <,... 在基础类型
我在 class 定义之外添加了运算符 <<。
我需要以下代码:
#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;
}
问题:如果基础类型不实现运算符<<,则Value 中的运算符<< 被称为递归无穷大。我想得到一个编译器错误,例如 no match for call operator<<(std:ostream&, const Value&)... 将 SFINAE 与我的访问者模式一起使用(此处未显示)。
我需要的是:
[&](auto a) -> std::enable_if_t<addressof?(os << a) != addressof?(os << Value{})>::value> {
return os << a;
};
如果功能相同,则禁用此 lambda。这可能吗?
没有价值的解决方案:
- 使值明确
- 禁止使用 Foo 构造值
你可以替换
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{});
~~~~~~~^~~~~~~
...
您可以添加一个包装器以强制仅进行一次转换:
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;
}
在不修改 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
我有
a class Value 可以用不同的类型构造 (Foo, Bar, int,...).
class Value 应该有常见的操作,比如 <<, ==, <,... 在基础类型
我在 class 定义之外添加了运算符 <<。
我需要以下代码:
#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;
}
问题:如果基础类型不实现运算符<<,则Value 中的运算符<< 被称为递归无穷大。我想得到一个编译器错误,例如 no match for call operator<<(std:ostream&, const Value&)... 将 SFINAE 与我的访问者模式一起使用(此处未显示)。
我需要的是:
[&](auto a) -> std::enable_if_t<addressof?(os << a) != addressof?(os << Value{})>::value> {
return os << a;
};
如果功能相同,则禁用此 lambda。这可能吗?
没有价值的解决方案:
- 使值明确
- 禁止使用 Foo 构造值
你可以替换
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{}); ~~~~~~~^~~~~~~ ...
您可以添加一个包装器以强制仅进行一次转换:
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;
}
在不修改 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&