Different behavior observed with constexpr auto/char-array variable - c++

Following up with this question Having a constexpr static string gives a linker error
In the question, this code wasn't able to compile:
#include <iostream>
struct Test { static constexpr char text[] = "Text"; };
int main()
{
std::cout << Test::text << std::endl; // error: undefined reference to `Test::text'
}
From the comment, this code is able to compile:
#include <iostream>
struct Test { static constexpr auto text = "Text"; };
int main()
{
std::cout << Test::text << std::endl;
}
My question is why the auto version works but the array of char version doesn't?
Could you please point out the statement in the standard allowing the second version and disallowing the first?
I took a look at Strange behavior with constexpr static member variable but it seems to be another question.

A declaration of a static data member in class is never a definition.
The difference between your examples is that only one requires a definition of text.
The auto version deduces char const*, hence text is only subject to an lvalue-to-rvalue conversion and not odr-used. By contrast, the first code effectively passes text's address, odr-use-ing it - i.e. necessitating a definition.

struct Test { static constexpr auto text = "Text"; };
resolves to
struct Test { static constexpr const char * text = "Text"; };
So the second expression is a constexpr value of a pointer not an array.

Related

template static constexpr definition of odr-used variable

Actual code is more complex but I was able to reduce it to this example.
Everything works fine until I try to take a pointer to MyPackets_t::types (uncomment call to foo() in main() )
At this point in order for the application to link, types requires a definition.
I'm struggling with correct syntax for the definition. Commented out template... should do the trick. However it generetes an error " template arguments to 'PacketCollection::types' do not match original template".
Trying something like this - template<> constexpr int PacketCollection::types[]; - causes another linker error as though variable is being used in that line rather than declared.
I've tried using MyPackets_t instead of PacketCollection - same results.
If I make packet collection non-templated then everything compiles and runs as expected.
I feel that I'm either missing something extremely basic here or there is a bug in the compiler. I get this behavior with gcc 4.8 and 4.9.
Clang 3.5 has a sligtly different take on the situation: declaration of constexpr static data member 'types' requires an initializer.
The only workaround that I've found so far was to use
static constexpr std::array<int, 2> types() { return {1,2}; }
instead, but I don't like this solution. If variable workes in non-templated version (using the initializer from header) it should also work for templated one.
#include <iostream>
using namespace std;
class Packet_A
{
public:
static constexpr int TYPE = 1;
};
class Packet_B
{
public:
static constexpr int TYPE = 2;
};
template <typename T> class PacketCollection
{
public:
static constexpr size_t size = 2;
static constexpr int types[size] { 1, 2 };
};
typedef PacketCollection<Packet_A> MyPackets_t;
//template<typename T> constexpr int PacketCollection<Packet_A>::types[PacketCollection<Packet_A>::size];
void foo(const int* bar, size_t size)
{
if (size >= 2)
{
cout << bar[0] << bar[1];
}
}
int main(int argc, char* argv[])
{
cout << Packet_A::TYPE;
cout << MyPackets_t::types[0];
//foo(MyPackets_t::types, MyPackets_t::size);
return 0;
}
You should use T instead of Packet_A
template<typename T> constexpr int PacketCollection<T>::types[PacketCollection<T>::size];
^ ^
See live example.

static const member of a struct is not defined

Using an 03 standard-compliant compiler (safety-critical variant of gcc-3.3.2).
The standard says that static member objects must be defined (9.4.2 (4)). It also states that the one-definition rule holds, but no diagnostic is required (9.4.2 (5)). Is the following code valid?
struct fred
{
static const int JOE=1;
int m_joe;
fred() : m_joe(JOE) {}
};
That is, there is no "static const int fred::JOE;".
I ask because we have a case (apparently) where a static const int in a template class was never defined, and the code worked in some contexts, but not others. I replaced the static const int with an enum, and it worked in all cases.
Were we definitely in the Land of Undefined Behavior?
A static const int defines a compile-time constant; I'm afraid I can't refer to a specific part of the standard. The only time you need a definition for it is if you try to take the address of it or create a reference. If you use an enum instead, the compiler will create a temporary variable for you when you need a reference.
struct test
{
static const int one = 1;
enum { two = 2 };
};
void printint(const int & i)
{
cout << i << endl;
}
int main() {
printint(test::one); // error
printint(test::two); // no error
return 0;
}

static const member variable initialization

Looks like I can init a POD static const member, but not other types:
struct C {
static const int a = 42; // OK
static const string b = "hi"; // compile error
};
Why?
The syntax initializer in the class definition is only allowed with integral and enum types. For std::string, it must be defined outside the class definition and initialized there.
struct C {
static const int a = 42;
static const string b;
};
const string C::b = "hi"; // in one of the .cpp files
static members must be defined in one translation unit to fulfil the one definition rule. If C++ allows the definition below;
struct C {
static const string b = "hi";
};
b would be defined in each translation unit that includes the header file.
C++ only allows to define const static data members of integral or enumeration type in the class declaration as a short-cut. The reason why const static data members of other types cannot be defined is that non-trivial initialization would be required (constructor needs to be called).
string is not a primitive type (like int) but is a class.
Disallowing this is sensible; the initialisation of statics happens before main. And constructors can invoke all sorts of functions that might not be available on initialisation.
I'll sum up the rules about direct class initialization on C++98 vs C++11:
The following code is illegal in C++03, but works just as you expect in C++11. In C++11, you can think of it as the initializers being injected into each of the constructors of POD, unless that constructor sets another value.
struct POD {
int integer = 42;
float floating_point = 4.5f;
std::string character_string = "Hello";
};
Making the fields mutable static members will break the code in both standards, this is because the static guarantees there to be only one copy of the variable and thus we have to declare the members in a exactly one file, just like we would do with global variables using the referred using the extern keyword.
// This does not work
struct POD {
static int integer = 42;
static float floating_point = 4.5f;
static std::string character_string = "Hello";
};
int POD::integer = 42;
float POD::floating_point = 4.5f;
std::string POD::character_string = "Hello";
// This works
struct POD {
static int integer;
static float floating_point;
static std::string character_string;
};
int POD::integer = 42;
float POD::floating_point = 4.3f;
std::string POD::character_string = "hello";
If we try to make them, const static members a new array of rules arise:
struct POD {
static const int integer = 42; // Always works
static constexpr float floating_point = 4.5f; // Works in C++11 only.
static const std::string character_string = "Hello"; // Does not work.
constexpr static const std::string character_string = "Hello"; // Does not work (last checked in C++11)
// Like some others have also mentioned, this works.
static const std::string character_string;
};
// In a sourcefile:
const std::string POD::character_string = "Hello";
So, from C++11 onwards, it is allowed to make static constants of non-integer trivial types variable. Strings unfortunately does not fit the bill, therefore we cannot initialize constexpr std::strings even in C++11.
All is not lost though, as an answer to this post mentions, you could create a string class functions as a string literal.
NB! Note that you this is metaprogramming at its best, if the object is declared as constexpr static inside a class, then, as soon as you enter run-time, the object is nowhere to be found. I haven't figured out why, please feel free to comment on it.
// literal string class, adapted from: http://en.cppreference.com/w/cpp/language/constexpr
class conststr {
const char * p;
std::size_t sz;
public:
template<std::size_t N>
constexpr conststr(const char(&a)[N]) : p(a), sz(N-1) {}
// constexpr functions signal errors by throwing exceptions from operator ?:
constexpr char operator[](std::size_t n) const {
return n < sz ? p[n] : throw std::out_of_range("");
}
constexpr std::size_t size() const { return sz; }
constexpr bool operator==(conststr rhs) {
return compare(rhs) == 0;
}
constexpr int compare(conststr rhs, int pos = 0) {
return ( this->size() < rhs.size() ? -1 :
( this->size() > rhs.size() ? 1 :
( pos == this->size() ? 0 :
( (*this)[pos] < rhs[pos] ? -1 :
( (*this)[pos] > rhs[pos] ? 1 :
compare(rhs, pos+1)
)
)
)
)
);
}
constexpr const char * c_str() const { return p; }
};
Now you can declare a conststr directly in you class:
struct POD {
static const int integer = 42; // Always works
static constexpr float floating_point = 4.5f; // Works in C++11 only.
static constexpr conststr character_string = "Hello"; // C++11 only, must be declared.
};
int main() {
POD pod;
// Demonstrating properties.
constexpr conststr val = "Hello";
static_assert(val == "Hello", "Ok, you don't see this.");
static_assert(POD::character_string == val, "Ok");
//static_assert(POD::character_string == "Hi", "Not ok.");
//static_assert(POD::character_string == "hello", "Not ok.");
constexpr int compare = val.compare("Hello");
cout << compare << endl;
const char * ch = val.c_str(); // OK, val.c_str() is substituted at compile time.
cout << ch << endl; // OK
cout << val.c_str() << endl; // Ok
// Now a tricky one, I haven't figured out why this one does not work:
// cout << POD::character_string.c_str() << endl; // This fails linking.
// This works just fine.
constexpr conststr temp = POD::character_string;
cout << temp.c_str() << endl;
}
In C++17 :
If you can use static constexpr you will have the desired result, but if you cant : use the inline variable introduce by C++17.
In your case, since std::string does not have a constexpr constructor, the solution is inline static const std::string
Example :
#include <iostream>
int foo() { return 4;}
struct Demo
{
inline static const int i = foo();
inline static const std::string str = "info";
};
int main() {
std::cout << Demo::i << " " << Demo::str<< std::endl;
}
Live Demo

Initializing std::array<char,x> member in constructor using string literal. GCC bug?

The following example initializing a std::array <char, N> member in a constructor using a string literal doesn't compile on GCC 4.8 but compiles using Clang 3.4.
#include <iostream>
#include <array>
struct A {
std::array<char, 4> x;
A(std::array<char, 4> arr) : x(arr) {}
};
int main() {
// works with Clang 3.4, error in GCC 4.8.
// It should be the equivalent of "A a ({'b','u','g','\0'});"
A a ({"bug"});
for (std::size_t i = 0; i < a.x.size(); ++i)
std::cout << a.x[i] << '\n';
return 0;
}
On first impression it looks like a GCC bug. I feel it should compile as we can initialize a std::array<char, N> directly with a string literal. For example:
std::array<char, 4> test = {"bug"}; //works
I would be interested to see what the Standard says about this.
Yes, your code is valid; this is a bug in gcc.
Here's a simpler program that demonstrates the bug (I've replaced std::array<char, 4> with S and got rid of A, as we can demonstrate the bug just in function return (this makes the analysis simpler, as we don't have to worry about constructor overloading):
struct S { char c[4]; };
S f() { return {"xxx"}; }
Here we have a destination object of type S that is copy-initialized (8.5p15) from the braced-init-list {"xxx"}, so the object is list-initialized (8.5p17b1). S is an aggregate (8.5.1p1) so aggregate initialization is performed (8.5.4p3b1). In aggregate initialization, the member c is copy-initialized from the corresponding initializer-clause "xxx" (8.5.1p2). We now return to 8.5p17 with destination object of type char[4] and initializer the string literal "xxx", so 8.5p17b3 refers us to 8.5.2 and the elements of the char array are initialized by the successive characters of the string (8.5.2p1).
Note that gcc is fine with the copy-initialization S s = {"xxx"}; while breaking on various forms of copy- and direct-initialization; argument passing (including to constructors), function return, and base- and member-initialization:
struct S { char c[4]; };
S f() { return {"xxx"}; }
void g(S) { g({"xxx"}); }
auto p = new S({"xxx"});
struct T { S s; T(): s({"xxx"}) {} };
struct U: S { U(): S({"xxx"}) {} };
S s({"xxx"});
The last is particularly interesting as it indicates that this may be related to bug 43453.

Auto + static in-class constant initalization with meta-programming

Consider the following simplified template meta-programming code that implements an Angle class that is internally storing the modulo 360 degrees reduced value.
#include <iostream>
#include <typeinfo>
template<int N, int D>
struct Modulus
{
static auto const value = N % D;
};
template<int N>
struct Angle
{
static auto const value = Modulus<N, 360>::value; // ERROR
//static int const value = Modulus<N, 360>::value; // OK
//static auto const value = N % 360; // OK
typedef Angle<value> type;
};
int main()
{
std::cout << typeid(Angle<30>::type).name() << "\n";
std::cout << typeid(Angle<390>::type).name() << "\n";
return 0;
}
Output on Ideone
With Visual C++ 2010 Express, I can do static auto const = Modulus<N, 360>::value, but with MinGW gcc 4.7.2 (Nuwen distro) or Ideone (gcc 4.5.1) I have to either explicitly denote the type as static int const value = Modulus<N, 360>::value or I have to use auto with the full modular expression as static auto const value = N % 360;.
Question: Which compiler is correct acccording to the new C++11 Standard?
The code is valid. Visual C++ is right to accept it and gcc is wrong to reject it (for completeness, Clang 3.1 also accepts the code). The specification states that (C++11 7.1.6.4[dcl.spec.auto]/4):
The auto type-specifier can also be used...in declaring a static data member with a brace-or-equal-initializer that appears within the member-specification of a class definition.
Your value is a static data member. It has a brace-or-equal-initializer (that is the = Modulus<N, 360>::value part of the declaration), and the initializer appears within the member-specification of the class definition (i.e., it's what mortals might call an "inline initializer").