Reputation: 20764
Is it guaranteed that the make_string
object will be constructed before the call to GetLastError
function in the following code:
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());
Upvotes: 2
Views: 142
Reputation: 62613
No, it is not guranteed. make_string() << GetLastError()
is semantically equivalent to the function call operator<<( make_string(), GetLastError() )
, and order of evaluation of function arguments is unspecified.
So, the compiler can first create an instance of make_string
, then call GetLastError()
, and then call a member function of said make_string
object, or it could first call GetLastError()
, then create an instance, and then call the member function. In my experience, the second outcome is more likely.
EDIT
There is also an interesting question raised couple of times in comments, which I believe is worth addressing.
The claim is, that since operator<<
is a member function, the whole statement is semantically the same as
make_string().operator<<(GetLastError());
This claim is, indeed, true. However, there is no sequencing in the above statement! What happens first - GetLastError()
call or make_sequence
constructor is undefined because of lack of sequencing here.
Upvotes: 8
Reputation: 145457
The order of evaluation of function arguments is unspecified.
And make_string() << GetLastError()
is a call of the operator<<
function.
However, in order to guarantee that GetLastError
is called after a Windows API function, in a creation of an error message, you can use the built-in ||
operator, like this:
AnApiFunctionThatReturnsTrueOnSuccess()
|| fail( "Bah, it failed", GetLastError() );
The order of evaluation is guaranteed here because the built-in ||
has short circuit behavior. The right hand side argument is only evaluated if the left hand argument evaluates to false
.
Then the fail
function can look like
auto fail( std::string const& s, int const code = 0 )
-> bool
{
throw std::runtime_error( make_string() << s << " (code = " << code << ")" );
}
doing the make_string()
at a point guaranteed after GetLastError()
, so that any call to e.g. an API level allocation function won't invalidate the result from GetLastError
.
Upvotes: 3
Reputation: 275966
An overloaded <<
is just a function call, and order of evaluation in unspecified. In your case, the ctor of your make_string
is empty (other than constructing an oss), so it does not matter: in your real code, this probably is not true.
A way around this is:
foo(make_string() << [&]{return GetLastError();});
then:
template<class T> struct tag{using type=T;};
and in the object:
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;
}
where if we are passed a callable, we invoke it.
This is manual short-circuit evaluation.
Some of the code above may contain typos or similar errors: the design is sound. It assumes result_of
is SFINAE enabled, as C++14 demands. There are other ways to detect if something can be invoked with 0 arguments if your result_of
fails.
Upvotes: 1