C++ Linker Error with Class Static Constexpr

C++ Linker Error With Class static constexpr

I don't think this is a bug. If you change the constexpr to const, it still fails, with the exact same error.

You've declared S::X, but not defined it anywhere, so there's no storage for it. If you do anything with it that needs to know the address of it then you'll need to define it somewhere also.

Examples:

int main() {
int i = S::X; // fine
foo<S::X>(); // fine
const int *p = &S::X; // needs definition
return std::min(S::X, 0); // needs it also
}

The reason for this is that constexpr can be evaluated at compile time, but it's not required to be evaluated as such, and can equally happen at runtime. It doesn't instruct "the compiler to always treat the symbol as an expression", it hints that it would be sensible and permissible to do so if the compiler felt like it.

Linker error for constexpr static member variable in gcc and clang

Before C++17

All the initializations of As invoke the A::A(B ) constructor, which require copying a B, which uses the compiler-generated B::B(B const&). Binding a variable to reference (A::bbb) is an odr-use of that variable. The specific rule, from [basic.def.odr] is:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the
lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) 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).

The first call to the copy constructor would involve binding A::bbb to a reference, which is neither an lvalue-to-rvalue conversion nor a discarded-value expression, so it's odr-used.

The most important rule is:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement (6.4.1); no diagnostic required.

A::bbb is odr-used, but lacks a definition, so we're violating that rule - commonly referred to as an odr violation. But since the compiler is not required to issue a diagnostic in this case ("no diagnostic required", or NDR for short), the program is undefined behavior. These kinds of issues can be frustrating at times for a programmer, but they're arbitrarily difficult for the compiler to diagnose - so it's something that we have to live with.

It is likely that on higher optimization levels, the compilers simply elide the copy, so a call to B::B(B const&) for A::bbb isn't necessary... As to why different initializations are treated differently? Probably as a result of different optimization passes. Ultimately, it doesn't change the fact that this is an odr violation - regardless of whether the code compiles and links.



After C++17

As a result of p0386, static constexpr data members are implicitly inline, which means that now A::bbb does have a definition and now there is no odr-violation. C++17 is cool.

Linker error with constexpr static member variable

constexpr static members can only be initialised inside the class body if the initialiser is also constexpr.

Your fun function contains a call to sin which, like most of the standard maths functions, is not constexpr.

Having a constexpr static string gives a linker error

Should work:

#include <iostream>

struct Test { static constexpr char text[] = "Text"; };
constexpr char Test::text[];

int main()
{
std::cout << Test::text << std::endl;
}

In standard (n4140 §9.4.2/3) you can find:

A static data member of literal type can be declared in the class
definition with the constexpr specifier
; if so, its declaration shall
specify a brace-or-equal-initializer in which every initializer-clause
that is an assignment-expression is a constant expression. [ Note: In
both these cases, the member may appear in constant expressions. —end
note ] The member shall still be defined in a namespace scope if it is
odr-used (3.2) in the program and the namespace scope definition shall
not contain an initializer
.

Undefined reference error for static constexpr member

This behaviour is vexing me time and again. The cause of the trouble is that your

A(int n): v(n, A::kDefaultValue) {}

odr-uses the static constexpr member, since the constructor of v takes a constant reference second argument. Odr-usage requires a definition somewhere, i.e.

const int A::kDefaultValue;

in some compilation unit (which is compiled and linked to main()). This requirement has been dropped in C++17 and the corresponding definition (as above) deprecated.

However, a definition is not always possible (for example for members of class templates) and the simplest way to avoid both the definition and your error is

A(int n): v(n, int(A::kDefaultValue)) {}

which creates a temporary to be passed to the constructor of v (but since the latter is fully inline, the compiler may optimise that away).

Different compilation + linking errors for static and constexpr between clang and gcc

So, without inline variables, I was able to get something achieving your goals working. The basic idea is to have a "backend" struct to hold static members and then fully specialize that struct to your options. This method has the added benefit of causing a compiler error if you attempt to access a member of the backend using a template parameter that has not been defined yet.

The header file would look like

#ifndef TEMPLATED_HEADER_HPP
#define TEMPLATED_HEADER_HPP

namespace template_header {
/**
* the "primary" template of the backend struct
* notice I leave the variable we want undefined!
* if you prefer to have a default for all other values of dim1
* you would put that here
*/
template<int dim1>
struct backend {};

template<>
struct backend<2> {
static constexpr int dim2 = 3;
};

template<>
struct backend<3> {
static constexpr int dim2 = 5;
};

/**
* Helper constexpr
* this is optional, but makes code outside this file more readable
* also, I named it in a way for your source files to be unchanged.
*/
template <int dim1>
constexpr dim2 = backend<dim1>::dim2;
}

#endif

I have a working version of this idea on Compiler Explorer which verifies that both this works with both GCC and clang. If you were to try to call dim2<dim1> where dim1 does not equal 2 or 3, then a compiler error complaining about backend<dim1>::dims not defined will be generated. You could add other members to the backend specializations, taking care to keep the names the same across the different values of dim1.

Total side note, why are you setting ar1 = {0};? From my reading of the std array reference, this would only set the first element in the array to 0.

Constexpr class function definition linking error

constexpr functions are implicitly inline and therefore need to be defined in every translation unit where they are used. That usually means that the definition should go into a header shared between the translation units using the function.

Also, in order for constexpr to be of any use, the function needs to be defined before its potential use in a constant expression. Therefore it usually only really makes sense to define it directly on the first declaration, i.e. here in the class definition in the header.



Related Topics



Leave a reply



Submit