C++ 表达式 SFINAE 和 ostream 操纵器
C++ expression SFINAE and ostream manipulators
我正在尝试学习如何使用 SFINAE。
出于练习目的,我试图制作一个 std::ostream
包装器以制作自定义格式化程序。
这是我的 SFINAE 和自定义输出 class。
// Tester
template <class O>
struct is_ostreamable {
template <class T>
static auto check(T t) -> decltype(std::declval<std::ostream &>() << t, std::true_type());
template <class>
static auto check(...) -> std::false_type;
public:
static constexpr bool value{std::is_same_v<decltype(check<O>(0)), std::true_type>};
};
// Custom class
struct CustomOutput {
// Constructor etc...
CustomOutput(std::ostream &os = std::cout) : os{os} {}
std::ostream &os;
// Problematic template function
template <class O, class = std::enable_if_t<is_ostreamable<O>::value>>
CustomOutput &operator<<(O o) {
os << o;
return *this;
}
};
struct
或 class
无法通过 operator<<
打印的模板不启用是完美的。
但是,对于这个 SFINAE,ostream 操纵器不工作......我不明白为什么。
错误,以及我的期望:
int main(void){
CustomOutput{} << "hi"; // Fine
std::vector<int> vec;
// CustomOutput{} << vec; // Error. Expected
CustomOutput{} << std::endl; // Error. WHY?
}
也许我错过了什么?任何帮助将不胜感激。
首先,修复您的 ostreamable
class。目前,您的 class 要求 T
可以从 0
复制构造。许多 classes 并非如此。它应该使用 std::declval
来创建值:
template <class O>
struct is_ostreamable {
template <class T>
static auto check(int) -> decltype(std::declval<std::ostream &>() << std::declval<T>(), std::true_type());
template <class>
static auto check(...) -> std::false_type;
public:
static constexpr bool value{std::is_same_v<decltype(check<O>(0)), std::true_type>};
};
这里做了两处改动:
decltype
的操作数使用 std::declval<T>()
创建类型 T
的对象。 std::declval<T>
是一个(故意未定义的)函数模板,当在未计算的操作数(例如 decltype
或 sizeof
、noexcept
运算符等)而不依赖于特定的构造签名(在您的情况下,从 0
复制构造)。
check
的参数替换为 int
。 value
变量的初始化器调用 check
参数 0
,所以这个 int
参数确保 (int)
在重载中排名高于 (...)
分辨率,以便尽可能选择 true_type
重载。
您需要为函数式操纵器(std::endl
、std::flush
等)提供特殊重载:
using manip = std::ostream& (*)(std::ostream&);
CustomOutput& operator<<(manip m) {
os << m;
return *this;
}
遗憾的是,无法使通用模板版本支持此功能。这是因为 std::endl
是函数模板:
template <class CharT, class Traits>
std::basic_ostream<CharT, Traits>& endl(td::basic_ostream<CharT, Traits>& os);
要使用函数模板,必须确定适当的模板参数。无法将类型模板参数 T
推断为通用模板。
无论如何,这可能是您唯一需要的特殊重载。
我知道已经有一个可接受的答案,但我想提一下使用概念做同样事情的更漂亮的 C++20 方法:
#include <iostream>
#include <concepts>
using OManipulator= std::ostream&(&)(std::ostream &);
template <typename T>
concept OStreamable = requires(T t) {
std::declval<std::ostream&>() << t;
};
struct CustomOutput {
std::ostream &os;
CustomOutput(std::ostream &os = std::cout)
: os{os}
{}
template <typename T> requires OStreamable<T>
CustomOutput& operator<<(T out) {
os << out;
return *this;
}
CustomOutput& operator<<(OManipulator out) {
os << out;
return *this;
}
};
int main(void){
CustomOutput{} << "hello";
CustomOutput{} << std::endl;
CustomOutput{} << "world";
}
基本上,在 C++20 中,需要以与 C++20 之前的版本相同的方式解决操纵器问题,方法是为它们提供特殊的重载。
我正在尝试学习如何使用 SFINAE。
出于练习目的,我试图制作一个 std::ostream
包装器以制作自定义格式化程序。
这是我的 SFINAE 和自定义输出 class。
// Tester
template <class O>
struct is_ostreamable {
template <class T>
static auto check(T t) -> decltype(std::declval<std::ostream &>() << t, std::true_type());
template <class>
static auto check(...) -> std::false_type;
public:
static constexpr bool value{std::is_same_v<decltype(check<O>(0)), std::true_type>};
};
// Custom class
struct CustomOutput {
// Constructor etc...
CustomOutput(std::ostream &os = std::cout) : os{os} {}
std::ostream &os;
// Problematic template function
template <class O, class = std::enable_if_t<is_ostreamable<O>::value>>
CustomOutput &operator<<(O o) {
os << o;
return *this;
}
};
struct
或 class
无法通过 operator<<
打印的模板不启用是完美的。
但是,对于这个 SFINAE,ostream 操纵器不工作......我不明白为什么。
错误,以及我的期望:
int main(void){
CustomOutput{} << "hi"; // Fine
std::vector<int> vec;
// CustomOutput{} << vec; // Error. Expected
CustomOutput{} << std::endl; // Error. WHY?
}
也许我错过了什么?任何帮助将不胜感激。
首先,修复您的 ostreamable
class。目前,您的 class 要求 T
可以从 0
复制构造。许多 classes 并非如此。它应该使用 std::declval
来创建值:
template <class O>
struct is_ostreamable {
template <class T>
static auto check(int) -> decltype(std::declval<std::ostream &>() << std::declval<T>(), std::true_type());
template <class>
static auto check(...) -> std::false_type;
public:
static constexpr bool value{std::is_same_v<decltype(check<O>(0)), std::true_type>};
};
这里做了两处改动:
decltype
的操作数使用std::declval<T>()
创建类型T
的对象。std::declval<T>
是一个(故意未定义的)函数模板,当在未计算的操作数(例如decltype
或sizeof
、noexcept
运算符等)而不依赖于特定的构造签名(在您的情况下,从0
复制构造)。check
的参数替换为int
。value
变量的初始化器调用check
参数0
,所以这个int
参数确保(int)
在重载中排名高于(...)
分辨率,以便尽可能选择true_type
重载。
您需要为函数式操纵器(std::endl
、std::flush
等)提供特殊重载:
using manip = std::ostream& (*)(std::ostream&);
CustomOutput& operator<<(manip m) {
os << m;
return *this;
}
遗憾的是,无法使通用模板版本支持此功能。这是因为 std::endl
是函数模板:
template <class CharT, class Traits>
std::basic_ostream<CharT, Traits>& endl(td::basic_ostream<CharT, Traits>& os);
要使用函数模板,必须确定适当的模板参数。无法将类型模板参数 T
推断为通用模板。
无论如何,这可能是您唯一需要的特殊重载。
我知道已经有一个可接受的答案,但我想提一下使用概念做同样事情的更漂亮的 C++20 方法:
#include <iostream>
#include <concepts>
using OManipulator= std::ostream&(&)(std::ostream &);
template <typename T>
concept OStreamable = requires(T t) {
std::declval<std::ostream&>() << t;
};
struct CustomOutput {
std::ostream &os;
CustomOutput(std::ostream &os = std::cout)
: os{os}
{}
template <typename T> requires OStreamable<T>
CustomOutput& operator<<(T out) {
os << out;
return *this;
}
CustomOutput& operator<<(OManipulator out) {
os << out;
return *this;
}
};
int main(void){
CustomOutput{} << "hello";
CustomOutput{} << std::endl;
CustomOutput{} << "world";
}
基本上,在 C++20 中,需要以与 C++20 之前的版本相同的方式解决操纵器问题,方法是为它们提供特殊的重载。