4
votes

The following code compiles fine on Visual Studio 2019 and on gcc 10.2 (and other gcc versions) with -std=c++11 but fails on clang (versions 9, 10 and 11).

#include <map>
#include <string>

struct repo {
    static constexpr const char *x = "sth";
};

int main() {

    // 1) This compiles
    std::map<std::string, int> m1 = { {repo::x, 3} };
    
    // 2) This compiles
    std::map<std::string, std::string> m2 = { std::make_pair(repo::x, "") };
    
    // 3) This does not compile on clang
    std::map<std::string, std::string> m3 = { {repo::x, ""} };

    return 0;
}

The error from clang is:

... undefined reference to `repo::x'
clang-11: error: linker command failed with exit code 1 (use -v to see invocation)
Compiler returned: 1

There are similar questions on SO on this, i.e. Undefined reference error, static constexpr data member, but none that explain to me why this code does not compile on clang. Is there an issue with the code in 3) above?

3
@NathanOliver Thanks, I was reading that before posting but I think this is different. For example, 1) compiles on clang but 3) does not. If the linked answer was the issue, neither should compile for that reason. - Francis

3 Answers

3
votes

C++17 introduces the rule that static constexpr member variables are implicitly inline (P0386). Inline variables didn't exist before C++17.

This means that in earlier C++ standards, a compiler may need a static constexpr member variable to be defined. If its address is taken, for example.

In C++ standards earlier than C++17, you can ensure that your code is well-formed, by separately defining the static variable.

struct repo {
    static constexpr const char *x = "sth";
};

constexpr const char *repo::x;

Edit:

It should be noted that with optimizations off, none of the examples successfully link.

It is an artifact of the optimizer that sometimes, a reference to a value can be flattened to the value itself, in which case the linker won't look for the missing symbol.

2
votes

Pre-C++17, you need to have a definition of static constexpr variable if it is ODR-used.

In your example, x is ODR-used, since it is passed to as an argument to pair constructor, which takes values by reference. Thus, you need a definition for it.

1
votes

Using https://godbolt.org/z/edj6Ed I verified that the program compiles & runs for gcc >= 7, but fails with the same error as your clang for 6.4, provided that one uses

-std=c++17

(actually I used -pedantic -std=c++17).

Clang++ also compiles your program fine starting from version 5.0.0, the first to support C++17. It always fails with std=C++14.

What happened in C++17 that refers to static constexpr?

A constexpr specifier used in a function or static member variable (since C++17) declaration implies inline.

(see https://en.cppreference.com/w/cpp/language/constexpr )

See also: https://www.codingame.com/playgrounds/2205/7-features-of-c17-that-will-simplify-your-code/inline-variables