15

In a discussion on another question, I was given an example where apparently linkage of an identifier affected its usability in a constant expression:

extern char const a[] = "Alpha";
char constexpr b[] = "Beta";
char const g[] = "Gamma";

template <const char *> void foo() {}

auto main()
    -> int
{
    foo<a>();     // Compiles
    foo<b>();     // Compiles
    foo<g>();     // Doesn't compile
}

The error from the last (with GCC) is:

test.cc: In function 'int main()':
test.cc:12:13: error: the value of 'g' is not usable in a constant expression
         foo<g>();     // Doesn't compile
             ^
test.cc:3:16: note: 'g' was not declared 'constexpr'
     char const g[] = "Gamma";
                ^

I may have missed the significance of the example in the earlier discussion, because I believed that it could not have been just linkage which differentiated foo<a> from foo<g> - however, I have begun to doubt that position.

  1. Is it really the linkage, or is it some other attribute granted by extern, that allows foo<a>()?
  2. What is the rationale for allowing foo<a>() but not foo<g>()? In particular, if it is determined by linkage, why should internal linkage cause a variable not to be usable as a constant expression when the same variable declared extern would be usable?
  3. It was suggested that the question of the symbol being visible (or not) to the linker is relevant here. To me, it seems that the fact the foo<b> variant is still allowed even when static is added disproves this - or am I mistaken?
  4. (The difference between foo<b>() and foo<g>() is adequately covered by other questions, I think).
Community
  • 1
  • 1
davmac
  • 20,150
  • 1
  • 40
  • 68

2 Answers2

6

GCC bug.

N3337 (which is C++11 + editorial fixes) [temp.arg.nontype]/2 has an example that's directly on point:

template<class T, const char* p> class X {
    /* ... */
};
X<int, "Studebaker"> x1; // error: string literal as template-argument

const char p[] = "Vivisectionist";
X<int,p> x2; // OK

In C++03 reference/pointer template arguments are limited to things with external linkage, but that restriction was removed in C++11.

The rules for reference/pointer template arguments are relaxed in C++17 to permit all constant expressions, so probably the reason why GCC accepts the example with -std=c++1z is that it goes through a different code path in that mode.

T.C.
  • 133,968
  • 17
  • 288
  • 421
3

This is a weird coincidence. I was just reading about this in C++ Templates just last night. When using a pointer as a template non-type parameter, it is the address contained in the pointer rather than the value pointed-to by the pointer that is the constant substituted as the template argument. Thus, the address must be knowable at compile time and unique across all compilation units to avoid ODR violations. This is true of constexpr and extern variables, but not those with file or global linkage. Here's an example.

static char const foo[] = "Hello";
char const bar[] = "Hello";
constexpr char const baz[] = "Hello";
extern char const qux[] = "Hello";

template <char const*>
struct my_struct{};

int main() {
    my_struct<foo> f;       // ERROR: Address is unique, but not known until runtime
    my_struct<bar> b;       // ERROR: Address may or may not be unique (ODR violation) and not known until runtime
    my_struct<baz> bz;      // OK: constexpr
    my_struct<qux> q;       // OK: extern
}
Tim
  • 1,517
  • 1
  • 9
  • 15
  • This is not true since C++11 removed the linkage restriction. – T.C. Oct 24 '16 at 21:59
  • 1
    In all four examples, the linker decides the address of the variables. The compiler never knows what other compilation units will be mixed together to create the final executable. – brian beuning Oct 24 '16 at 22:13
  • @T.C. I had tested my code with gcc-6.2 using -std=c++14, but it looks like gcc has a bug in this area. I should have used a second compiler. I knew that C++17 will add support for `nullptr` as a non-type argument, but I wasn't aware that the linkage rules had changed so much in C++11. – Tim Oct 25 '16 at 23:53
  • @brianbeuning To be super pedantic, the loader decides the addresses. :D Indeed, the linker will determine the offsets within the executable based on the layouts of the individual objects generated by the compiler when it fuses the sections together. It is within these objects that I am saying the compiler will "know" the address at instantiation time. Admittedly, though, I don't know exactly how the compiler is able to differentiate between `foo` and `bar` at the point of instantiation. It is certainly an implementation detail, but one I would be interested in learning about. – Tim Oct 26 '16 at 00:02
  • 2
    @Peregring-lk If `bar` is defined multiple times with external linkage in multiple translation units, then you would have an ODR violation and the linker would either just pick one (greedy resolution), quit with an error (not required by the standard), or do something else entirely (maybe some sort of heuristic). – Tim Oct 26 '16 at 00:05
  • @Tim I think the key about bar[] is some other compilation unit might define it as char const bar[] = "World!"; and the compiler would never know. Not all linkers will complain. For the pedantic point, the loader decides for code compiled with -fpic but code compiled without -fpic must be loaded where the linker says to load it. – brian beuning Oct 26 '16 at 14:21