Asked  7 Months ago    Answers:  5   Viewed   163 times

I want to have a static const char array in my class. GCC complained and told me I should use constexpr, although now it's telling me it's an undefined reference. If I make the array a non-member then it compiles. What is going on?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}

 Answers

24

Add to your cpp file:

constexpr char foo::baz[];

Reason: You have to provide the definition of the static member as well as the declaration. The declaration and the initializer go inside the class definition, but the member definition has to be separate.

Tuesday, June 1, 2021
 
superhero
answered 7 Months ago
53

The One Definition rule tells us that we can not have more than one definition of an odr-used variable in a program. So if a variable is odr-used then you need to define it but you can not define it the header file since it may be included more than once with the whole program. Odr-use violations do not require a diagnostic message and so you can violate this rule and the compiler is not obliged to notify you.

In your case you are indeed odr-using str_, and you can not include the definition in the header file because that would violate the one definiton rule since it can be included more than once within the program.

It is interesting to note that if you had done the following it would not have been odr-used:

return str_.size_;

You would therefore not need to define the variable, which can have some odd consequences in some examples. I doubt that really solves your problem long-term.

The odr rules are covered in the draft C++ standard section 3.2 and they say:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.19) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression (Clause 5). this is odr-used if it appears as a potentially-evaluated expression (including as the result of the implicit transformation in the body of a non-static member function (9.3.1)).[...]

So str_ yield a constant expression, the lvalue-to-rvalue conversion is not applied the expression str_.size() and it is not a discarded value expression, so it is odr-used and therefore str_ is required to be defined.

On the other hand the lvalue-to-rvalue conversion is applied to the expression str_.size_, so it is not odr-used and does not require str_ to be defined.

Tuesday, July 20, 2021
 
Nate
answered 5 Months ago
20

you need to initialize the static member, add std::queue<int> A::bufferInbound; after the class or move it inside your function.

Monday, August 23, 2021
 
PeanutsMcgee
answered 4 Months ago
87

You need to define A::L outside its class in a source file

constexpr size_t A::L;

Live example using Clang

For header-only code, and if your class A is not already a template, you can define a class template A_<T> with a void default value, and write a typedef for A in terms of that

template<class = void>
struct A_
{
    static constexpr size_t L = 4;

    template <typename T>
    void member_ref(T&& x) { cout << std::forward<T>(x) << endl; }

    template <typename T>
    void member_val(T x) { cout << x << endl; }

};

template<class T>
constexpr size_t A_<T>::L;

using A = A_<>;

Live Example.

NOTE: this business can involve a fair amount of boiler-plate. It is good to note that one can write

template
<
    class MyConcept1, 
    class MyConcept2, 
    class YetAnotherConcept
    // more long and well-documented template parameter names
>
struct A
{
    // many static constexpr variabels
};

template<class P1, class P2, class P3 /* many more short parameter names */>
constexpr SomeType A<P1, P2, P3, /* many more */>::some_var;

// many more one-liners.

Template parameters just have formal names, they don't have to be the same everywhere (just put them in the right order everywhere, though!).

Monday, August 23, 2021
 
octern
answered 4 Months ago
46

An object or function must be defined if it is odr-used. In some cases objects and functions are not odr-used and in those cases you don't have to define them. Whether or not the declaration of a static class member has an initializer, it is still not a definition. In all cases the rule is that an out-of-class definition at an enclosing namespace scope is required if the static member is odr-used.

The intuition is that "odr-used" means "the linker needs its address". If a constexpr variable is only used in ways that require its value---and never its address---then it may avoid being odr-used. In such cases the compiler simply inlines its value, and it does not need to be defined, because the linker doesn't need its address. That's the case with const char* Something<int>::str, but not const char Something<int>::str[].

"But they're the same!", you shout. Not so. For when str is a const char*, its value is the address of the string literal "int". The address of the string literal is needed, but not the address of str itself. The former is the value of str and it satisfies the requirements for not being odr-used; the compiler can just inline it. But when str is a const char[], its value is the string literal "int" itself. When you try to output it using istream::operator<<, it is implicitly converted into a const char*. But the const char*'s value is the address of the string literal, that is, the address of Something<int>::str. Therefore in this case Something<int>::str is odr-used; its address is needed.

There is logic in the standard that can be used to determine precisely when a variable is odr-used ([basic.def.odr]). But I'm not going to quote it because it's the most confusing section of the entire standard. I will say that in the case where you have const char* Something<int>::str, the lvalue-to-rvalue conversion is immediately applied, which is one of the conditions for it to not be odr-used; and in the case where you have const char Something<int>::str[], the array-to-pointer conversion is immediately applied, and that doesn't satisfy the condition.

Saturday, November 13, 2021
 
Dave Nottage
answered 3 Weeks ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share