cv-qualified struct's member is not similarly cv-qualified - c++

According to this answer, the following code should be compiled without error:
#include <type_traits>
namespace
{
struct A { int i; };
volatile A a{};
static_assert(std::is_volatile< decltype(a) >{});
static_assert(std::is_volatile< decltype(a.i) >{});
}
but there is a hard error:
main.cpp:10:1: error: static_assert failed
static_assert(std::is_volatile< decltype(a.i) >{});
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Live example with clang 3.6.0.
Is it a clang bug or am I missing something substantial?
ADDITIONAL:
#include <type_traits>
namespace
{
struct A { int i; };
const A a{};
static_assert(std::is_const< decltype(a) >{});
static_assert(std::is_const< decltype(a.i) >{});
}
Exactly the same behaviour for the latter code snippet.
ADDITIONAL:
static_assert(std::is_volatile< std::remove_pointer_t< decltype(&a.i) > >{});
not cause an error.

This is the correct behaviour; the current decltype isn't testing what you think it tests. You have to change your decltype if you want to test for volatile-ness.
It is possible to copy a value out and drop volatile-ness. But a reference must be volatile reference-to-volatile.
int foo() {
int i = a.i; // volatile not needed
volatile int &i = a.i; // volatile needed
}
The decltype is treating a.i as an rvalue, not an lvalue, doing what decltype does by default and is doing a "naive" text analysis and reporting the type of A::i as written in the source code; therefore, decltype(a.i) is int.
Putting () around an expression in decltype changes its behaviour (in good ways usually, for this kind of question), giving us an lvalue when appropriate. Therefore, decltype(( a.i )) is volatile int &.
Now, you might expect that is_volatile< volatile int & >::value would be true. But it's not. We need to remove the reference before we can test for the volatile-ness.
static_assert(std::is_volatile< std::remove_reference_t<decltype((a.i))> >{});
Finally, you might be surprised that volatile int & isn't volatile according to is_volatile. But I guess it's because references are really very like pointers - and the 'pointer' inside a reference isn't volatile, even if the object pointed to is volatile. That's my theory anyway. const int& also does not satisfy is_const.

Related

Legitimate to initialize an array in a constexpr constructor?

Is the following code legitimate?
template <int N>
class foo {
public:
constexpr foo()
{
for (int i = 0; i < N; ++i) {
v_[i] = i;
}
}
private:
int v_[N];
};
constexpr foo<5> bar;
Clang accepts it, but GCC and MSVC reject it.
GCC's error is:
main.cpp:15:18: error: 'constexpr foo<N>::foo() [with int N = 5]' called in a constant expression
15 | constexpr foo<5> bar;
| ^~~
main.cpp:4:15: note: 'constexpr foo<N>::foo() [with int N = 5]' is not usable as a 'constexpr' function because:
4 | constexpr foo()
| ^~~
main.cpp:4:15: error: member 'foo<5>::v_' must be initialized by mem-initializer in 'constexpr' constructor
main.cpp:12:9: note: declared here
12 | int v_[N];
| ^~
If this kind of code were OK, I could cut quite a few uses of index_sequences.
Trivial default initialisation was prohibited in a constexpr context until C++20.
The reason, I'm guessing, is that it is easy to "accidentally" read from default-initialised primitives, an act which gives your program undefined behaviour, and expressions with undefined behaviour are straight-up prohibited from being constexpr (ref). The language has been extended though so that now a compiler must check whether such a read takes place and, if it doesn't, the default-initialisation should be accepted. It's a bit more work for the compiler, but (as you've seen!) has substantial benefits for the programmer.
This paper proposes permitting default initialization for trivially default constructible types in constexpr contexts while continuing to disallow the invocation of undefined behavior. In short, so long as uninitialized values are not read from, such states should be permitted in constexpr in both heap and stack allocated scenarios.
Since C++20, it's legal to leave v_ "uninitialised" like you have. Then you've gone on to assign all its elements values, which is great.
While this does not directly answer your question, I thought it was at least worth mentioning. You could simply use in-class initialization and zero-initialize the array:
int v_[N]{};
Another way, without initializing the array first, is to (privately) inherit from std::array. Oddly enough, this actually gets accepted by GCC but not by Clang:
#include <array>
template<int N>
struct foo : private std::array<int, N> {
constexpr foo() {
for (auto i = int{}; i < N; ++i) {
(*this)[i] = i;
}
}
};
constexpr foo<5> bar;

Brace-initialization vs. Parenthesis Bug

I was filing a GCC bug for this, but I'd rather double-check this.
Consider the following programs:
#include <utility>
template<typename T, typename A>
void F(A&& a) { T(std::forward<A>(a)); } // Note: () syntax.
int main() { int i; F<int&>(i); }
and:
#include <utility>
template<typename T, typename A>
void F(A&& a) { T{std::forward<A>(a)}; } // Note: {} syntax.
int main() { int i; F<int&>(i); }
Latest Clang and MSVC compilers accept both programs. GCC 5 and beyond accept the first program but reject the second, claiming invalid cast of an rvalue expression of type 'int' to type 'int&'.
Is this a GCC bug? Or is this indeed a difference between T{} and T() in the above context (and thus a bug in Clang and MSVC)?
Edit:
The issue can be narrowed down to the following simpler excerpts:
int i; (int&){i};
and
int i; (int&)(i);
There are two separate issues:
The standard is unclear what T{x} should do for reference type T. Currently [expr.type.conv]/1 says that it creates a prvalue of type T, which is nonsense for reference types. This is core issue 1521.
The sane thing is probably to have T{x} for reference type T do roughly T __tmp{x}; and then yield the equivalent of static_cast<T>(__tmp) (so xvalue for rvalue reference T and lvalue for lvalue reference T). However, C++11 as published screwed up the specification for list-initialization of references, making it always create a temporary. The result was that int i; int &r{i}; failed to compile because it would attempt to bind r to a temporary copy of i, which is obviously nonsense. This is fixed by core issue 1288, whose resolution GCC is supposed to implement, but it looks like from the error message that it's not completely fixed.

constexpr member functions that don't use this?

Please consider the following two C++14 programs:
Program 1:
struct S { constexpr int f() const { return 42; } };
S s;
int main() { constexpr int x = s.f(); return x; }
Program 2:
struct S { constexpr int f() const { return 42; } };
int g(S s) { constexpr int x = s.f(); return x; }
int main() { S s; return g(s); }
Are neither, either or both of these programs ill-formed?
Why/why not?
Both programs are well-formed. The C++14 standard requires that s.f() be a constant expression because it is being used to initialize a constexpr variable, and in fact it is a core constant expression because there's no reason for it not to be. The reasons that an expression might not be a core constant expression are listed in section 5.19 p2. In particular, it states that the evaluation of the expression would have to do one of several things, none of which are done in your examples.
This may be surprising since, in some contexts, passing a non-constant expression to a constexpr function can cause the result to be a non-constant expression even if the argument isn't used. For example:
constexpr int f(int) { return 42; }
int main()
{
int x = 5;
constexpr auto y = f(x); // ill-formed
}
However, the reason this is ill-formed is because of the lvalue-to-rvalue conversion of a non-constant expression, which is one of the things that the evaluation of the expression is not allowed to do. An lvalue-to-rvalue conversion doesn't occur in the case of calling s.f().
I can't seem to find a compelling passage or example in the standard that directly addresses the issue of calling a constexpr member function on a non-constexpr instance, but here are some that may be of help (from draft N4140):
[C++14: 7.1.5/5]:
For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting
constexpr constructor, if no argument values exist such that an invocation of the function or constructor
could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no
diagnostic required.
constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required
From this I take that the program is not outright ill-formed just because a constexpr function has a possible non-constexpr path.
[C++14: 5.19]:
int x; // not constant
struct A {
constexpr A(bool b) : m(b?42:x) { }
int m;
};
constexpr int v = A(true).m; // OK: constructor call initializes
// m with the value 42
constexpr int w = A(false).m; // error: initializer for m is
// x, which is non-constant
This is somewhat closer to your example programs, here a constexpr constructor may reference a non-constexpr variable depending on the value of the argument, but there is no error if this path is not actually taken.
So I don't think either program you presented should be ill-formed, but I cannot offer convincing proof :)
This sounds like a quiz question, and not presented by a student, but the professor testing the public on stackoverflow, but let's see...
Let's start with the One Definition Rule. It's clear neither version violates that, so they both pass that part.
Then, to syntax. Neither have syntax failures, they'll both compile without issue if you don't mind the potential blend of a syntax and semantic issue.
First, the simpler semantic issue. This isn't a syntax problem, but f(), in both versions, is the member of a struct, and the function clearly makes no change to the owning struct, it's returning a constant. Although the function is declared constexpr, it is not declared as const, which means if there were some reason to call this as a runtime function, it would generate an error if that attempt were made on a const S. That affects both versions.
Now, the potentially ambiguous return g(S()); Clearly the outer g is a function call, but S may not be so clear as it would be if written return g(S{}); With {} initializing S, there would be no ambiguity in the future should struct S be expanded with an operator() (the struct nearly resembles a functor already). The constructor invoked is automatically generated now, and there is no operator() to create confusion for the compiler at this version, but modern C++14 is supposed to offer clearer alternatives to avoid the "Most Vexing Parse", which g(S()) resembles.
So, I'd have to say that based on semantic rules, they both fail (not so badly though).

Why does a const reference to volatile int need a static_cast?

Given the following code:
struct Foo {
volatile int i;
};
const int& bar = foo.i;
I get:
error: invalid initialization of reference of type 'const int&' from expression of type 'volatile int'
Unless I provide a static_cast<volatile int>.
"Volatility" of types in C++ works in pretty much exactly the same way as "const-ness" -- that is, an object that is volatile cannot be assigned to a non-volatile reference, in just the same way that
const int i = 3;
int& j = i; // won't work
Similarly, only methods marked volatile can be called on a volatile object:
struct S
{
void do_something() volatile {}
void do_something_else() {}
};
volatile S s;
s.do_something(); // fine
s.do_something_else(); // won't work
Methods can be overloaded by "volatility", in the same way they can be overloaded by const-ness.
In C++ standardese, these things are known as cv-qualifiers, to emphasise that they work in exactly the same way. (C99 adds a third that works in the same way, restrict, available as an extension in some C++ compilers). You can change cv qualifiers using const_cast<> -- the more powerful static_cast<> isn't required.
EDIT:
Just to make clear, a type can have both const and volatile modifiers at the same time, giving a total of four possibilities:
int i;
const int j;
volatile int k;
const volatile int l;
As with const, a volatile object can only be referred to by a volatile reference. Otherwise, the compiler has no way of knowing that accesses to the object via the reference need to be volatile.
volatile const int& bar = foo.i;
^^^^^^^^
Unless I provide a static_cast<volatile int>
It's rarely a good idea to silence a compiler error by adding casts. This doesn't give you a reference to foo.i, but to a separate copy of it.
If you are doing something really strange that requires a non-volatile reference to a volatile object, you'd need const_cast to remove the qualifer:
const int& weird = const_cast<int&>(foo.i);

Using reference as template type

In the following example, Foo is not doing what is intended, but I can't figure out why this is allowed to compile.
#include <string>
#include <iostream>
typedef std::string& T;
T Foo(int & i)
{
return T(i);
}
int main()
{
int a = 1;
std::string & s = Foo(a);
}
I discovered this with templates, but the typedef shows that its unrelated to templates. Needless to say, s is not a valid string here. I would think that constructing the value in the return of Foo would produce a compile error.
What am I missing here?
First of all, it worth nothing that the problem actually has no relationship to templates because this code compiles a well:
typedef std::string& T;
T Foo(int& i) {
return T(i);
}
The reason I think this compiles is that the return statement is equivalent to
return reinterpret_cast<T>(i);
in case T happens to be a reference members. ... and this, of course, compiles: You promised you knew what you were doing and asked the compiler kindly to believe you.
OK, found it at 5.2.3 [expr.type.conv] paragraph 1:
... 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). ...
... and 5.4 [expr.cast] paragraph 4:
The conversions performed by [other forms of casts] a reinterpret_cast (5.2.10) [...] can be performed using the cast notation of explicit type conversion. [...]
(the elisions cover cases involving user defined type, built-in type conversions, const conversions, etc.)
This has nothing to do with templates, you get the same result if T is just a typedef for std::string& rather than a deduced template parameter:
#include <string>
typedef std::string& T;
T Foo(int & i)
{
return T(i);
}
int main()
{
int a = 1;
std::string & s = Foo(a);
}
Dietmar's answer made me realise this can be further simplified to:
#include <string>
typedef std::string& T;
int main()
{
int a = 1;
std::string & s = T(a);
}
where T(a) is the same as the cast (T)a i.e. (std::string&)a which (according to the rules of 5.4 [expr.cast]) will do a const_cast if that's valid (which it isn't) or a static_cast if that's valid (which it isn't) or a static_cast followed by a const_cast if that's valid (which it isn't) or a reinterpret_cast if that's valid (which it is) or a reinterpret_cast followed by a const_cast if that's valid, otherwise the expression is ill-formed.
So as Dietmar said, it's the same as doing a reinterpret_cast, i.e.
std::string & s = reinterpret_cast<std::string&>(a);
I find it quite surprising that the original code compiles, but as it's the same as that line above, it's allowed to compile. Using the result of the cast is undefined behaviour though.
To avoid the surprise where T(a) is equivalent to a cast, use the new C++11 uniform initialization syntax, T{a}, which is always an initialization, not a cast expression.
Great question, investigating and answering it showed me a new gotcha I wasn't previously aware of, thanks to JaredC and Dietmar for the new piece of knowledge!