7

Is this code sample valid?

using ref = char&;

ref foo(ref x) {
  return ref{x};
}

int main() {
  char a;
  foo(a);
  return 0;
}

seems that:

  • clang 3.5 says YES
  • gcc 4.9 says NO

    main.cpp: In function 'char& foo(ref)':
    main.cpp:4:15: error: invalid cast of an rvalue expression of type 'char' to type 'ref {aka char&}'
       return ref{x};
                   ^
    

http://coliru.stacked-crooked.com/a/cb6604b81083393f

So which compiler is right? or is it unspecified?

It very easy so overcome gcc build error by:

  1. using parenthesis instead of braces

    ref foo(ref x) {
      return ref(x);
    }
    
  2. by naming returned value

    ref foo(ref x) {
      ref ret{x};
      return ret;
    }
    

option 1. breaks uniform initialization, option 2. adds useless line of code.

Similar question was already aked here: Why can't I initialize a reference in an initializer list with uniform initialization?

But mentioned pr50025 is fixed in gcc 4.9.

I know that above code sample is pretty useless, but I oversimplified it intentionally to point out the issue. In real life code problem can be hidden in a generic function like:

#include <utility>
template <typename Tp, typename... Us>
Tp bar(Us&&... us) {
  return Tp{std::forward<Us>(us)...};
}
Community
  • 1
  • 1
Karol Wozniak
  • 283
  • 1
  • 7

1 Answers1

3

This seems like an omission in the standard, where GCC is implementing exactly what the standard requires, and clang is going for what's probably intended.

From C++11 (emphasis mine):

5.2.3 Explicit type conversion (functional notation) [expr.type.conv]

1 A simple-type-specifier (7.1.6.2) or typename-specifier (14.6) followed by a parenthesized expression-list constructs a value of the specified type given the expression list. If the expression list is a single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4). [...]

[...]

3 Similarly, a simple-type-specifier or typename-specifier followed by a braced-init-list creates a temporary object of the specified type direct-list-initialized (8.5.4) with the specified braced-init-list, and its value is that temporary object as a prvalue.

For the braced-init-list case, the standard doesn't specify that that this works just like a C-style cast. And it doesn't:

typedef char *cp;
int main() {
  int i;
  (cp(&i)); // okay: C-style casts can be used as reinterpret_cast
  (cp{&i}); // error: no implicit conversion from int * to char *
}

Unfortunately, T(expr) being equivalent to (T)expr is also the one exception in which a functional cast doesn't necessarily produce a prvalue. The standard fails to specify a similar exception for a functional cast using a braced-init-list to a reference type. As a result, in your example, ref{x} constructs a temporary of type ref, direct-list-initialised from {x}. That temporary is then treated as a prvalue, because that's what the standard says the behaviour should be, and that prvalue cannot be used for binding to an lvalue reference.

I strongly suspect that if this were brought up to the ISO C++ committee, the standard would be changed to require clang's behaviour, but based on the current wording of the standard, I think it's GCC that's correct, at least for your specific example.

Instead of adding a variable, or switching to parentheses, you can omit ref (Tp) to avoid the problem:

template <typename Tp, typename... Us>
Tp bar(Us&&... us) {
  return {std::forward<Us>(us)...};
}
  • Omitting `Tp` in return statement discards `Tp`s with `explicit` constructors... generic function is not so generic any more – Karol Wozniak Jan 28 '15 at 22:08
  • @KarolWozniak That's a good point. On the other hand, what you already have (all three versions) requires an accessible copy or move constructor, so it's already not as generic as it could potentially be. I don't have a good alternative that covers all possible types, sorry. –  Jan 29 '15 at 10:28