是否保证在以下代码中调用make_string
函数之前构造GetLastError
对象:
class make_string
{
public:
template <typename T>
make_string& operator<<(const T& arg)
{
_stream << arg;
return *this;
}
operator std::string() const
{
return _stream.str();
}
protected:
std::ostringstream _stream;
};
// Usage
foo(make_string() << GetLastError());
答案 0 :(得分:8)
不,这是不保证的。 make_string() << GetLastError()
在语义上等同于函数调用operator<<( make_string(), GetLastError() )
,并且未指定函数参数的评估顺序。
因此,编译器可以先创建make_string
的实例,然后调用GetLastError()
,然后调用所述make_string
对象的成员函数,或者它可以先调用{{1}然后创建一个实例,然后调用成员函数。根据我的经验,第二个结果更有可能。
修改强>
在评论中也提出了一些有趣的问题,我认为值得解决。
声明是,由于GetLastError()
是一个成员函数,整个语句在语义上与
operator<<
这种说法确实是对的。但是,上述声明中没有排序!首先发生了什么 - make_string().operator<<(GetLastError());
调用或GetLastError()
构造函数未定义,因为此处缺少排序。
答案 1 :(得分:3)
未指定函数参数的评估顺序。
make_string() << GetLastError()
是对operator<<
函数的调用。
但是,为了保证在Windows API函数之后调用GetLastError
,在创建错误消息时,您可以使用内置的||
运算符,如下所示:
AnApiFunctionThatReturnsTrueOnSuccess()
|| fail( "Bah, it failed", GetLastError() );
此处保证评估顺序,因为内置||
具有短路行为。仅当左手参数评估为false
时,才会评估右侧参数。
然后fail
函数看起来像
auto fail( std::string const& s, int const code = 0 )
-> bool
{
throw std::runtime_error( make_string() << s << " (code = " << code << ")" );
}
在make_string()
后保证的点上执行 GetLastError()
,以便对... API级别分配功能不会使GetLastError
的结果无效。
答案 2 :(得分:1)
重载<<
只是一个函数调用,而未指定的评估顺序。在你的情况下,make_string
的ctor是空的(除了构造一个oss),所以没关系:在你的真实代码中,这可能不是真的。
解决这个问题的方法是:
foo(make_string() << [&]{return GetLastError();});
然后:
template<class T> struct tag{using type=T;};
并在对象中:
template<class F>
make_string& operator<<(const F& arg)
{
output(arg, std::result_of<F const&()>{});
return *this;
}
template<class F, class Test>
tag<typename Test::type> output(F const& f, Test const&) {
_stream << f();
return {};
}
template<class T, class...Unused>
void output(T const& t, Unused const&...) {
_stream << t;
}
如果我们传递一个可调用的,我们调用它。
这是手动短路评估。
上面的一些代码可能包含拼写错误或类似错误:设计合理。假设result_of
启用了SFINAE,正如C ++ 14所要求的那样。如果result_of
失败,还有其他方法可以检测是否可以使用0参数调用某些内容。