6

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?

Francis
  • 529
  • 4
  • 15
  • very related: https://stackoverflow.com/questions/40690260/undefined-reference-error-for-static-constexpr-member – NathanOliver Feb 01 '21 at 19:45
  • @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 Feb 01 '21 at 19:49

3 Answers3

6

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.

Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
  • Doesn't explain why in this particular example definition is required. – SergeyA Feb 01 '21 at 21:09
  • Thanks for the reference. Why does `std::map m1 = { {repo::x, 3} };` compile fine yet `std::map m3 = { {repo::x, ""} };` fails to compile. If it is an issue regarding the implicit inlining of `repo::x`, should this not equally affect the compilation of the line `std::map m1 = { {repo::x, 3} };`? – Francis Feb 01 '21 at 21:17
  • @Francis I have added an _edit_ that addresses what you mention. – Drew Dormann Feb 01 '21 at 22:11
  • 1
    @DrewDormann Thanks a lot. I am marking this as answer. This was the piece that was really confusing me. I never thought to remove the optimisation flag when playing around on https://godbolt.org. – Francis Feb 01 '21 at 22:15
2

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

zkoza
  • 2,644
  • 3
  • 16
  • 24
  • Thanks. However, I don't see this failing to compile on gcc versions >=7. For example, the following link shows compilation with gcc 10.2 and `-O2 -pedantic -std=c++17` e.g. https://godbolt.org/z/oWKhhs. – Francis Feb 01 '21 at 21:14
  • Additionally, I would have the same comment as I added to the answer https://stackoverflow.com/a/65999633/1771882 below. If the failure is due to implicit inlining of `repo::x`, should this not cause an error from `1)` also i.e. the line `std::map m1 = { {repo::x, 3} };`. – Francis Feb 01 '21 at 21:22
  • I'm not good at godbolot.org, but I think its primary purpose is to compile and disassembly a program, and for this linking is unnecessary. See this: https://godbolt.org/z/oq6cdd there's a third panel which tries to run the program and then all attempts with gcc and `-std=c++14` fail. It also fails on my machine if I use this flag manually, but I have an alias `g++='g++ -std=c++17'`, which made me think gcc behaves different than clang, for which I have no alias and which I use only occasionally. No. Gcc also reports an error. – zkoza Feb 01 '21 at 22:24
2

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.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • Thanks. I have to admit that I am not very clear on when a variable is ODR-used or not. Is `x` not ODR-used in the statement `std::map m1 = { {repo::x, 3} };` also? If it is, shouldn't compilation fail when only this part is included i.e. `2)` and `3)` are removed? – Francis Feb 01 '21 at 21:34