FrozenHeart
FrozenHeart

Reputation: 20764

Order of functions calls

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

Answers (3)

SergeyA
SergeyA

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

Cheers and hth. - Alf
Cheers and hth. - Alf

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

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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

Related Questions