12

I am trying to understand why I get a warning -Wsubobject-linkage when trying to compile this code:

base.hh

#pragma once

#include <iostream>

template<char const *s>
class Base
{
public:
  void print()
  {
    std::cout << s << std::endl;
  }
};

child.hh

#pragma once

#include "base.hh"

constexpr char const hello[] = "Hello world!";

class Child : public Base<hello>
{
};

main.cc

#include "child.hh"

int main(void)
{
  Child c;
  c.print();
  return 0;
}

When running g++ main.cc I get this warning:

In file included from main.cc:1:
child.hh:7:7: warning: ‘Child’ has a base ‘Base<(& hello)>’ whose type uses the anonymous namespace [-Wsubobject-linkage]
    7 | class Child : public Base<hello>
      |       ^~~~~

This warning does not occur if the templated value is an int or if child.hh is copied in main.cc I do not understand what justifies this warning here, and what I'm supposed to understand from it.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • Note getting any warning [here](https://godbolt.org/z/Wa1e6j1Gh) or [here](https://wandbox.org/permlink/jZIAJ5QEX5Y3Caa3). But getting the warning [here](https://wandbox.org/permlink/M7O7mJS1KJpPuJ05) – Jason Jun 29 '22 at 12:31
  • Are you sure you're compiling the code you think you are? You're not accidentally picking up some other version of `base.hh` or `child.hh`? `g++ -H main.cc` should provide some info about which headers are found where. – G.M. Jun 29 '22 at 12:36
  • @G.M. See we're getting the warning [here](https://wandbox.org/permlink/M7O7mJS1KJpPuJ05) – Jason Jun 29 '22 at 12:36
  • 2
    Seems to be a gcc [bug](https://gcc.gnu.org/bugzilla//show_bug.cgi?id=86491). – Jason Jun 29 '22 at 12:38

2 Answers2

8

The warning is appropriate, just worded a bit unclear.

hello is declared constexpr, as well as redundantly const. A const variable generally has internal linkage (with some exceptions like variable templates, inline variables, etc.).

Therefore hello in the template argument to Base<hello> is a pointer to an internal linkage object. In each translation unit it will refer to a different hello local to that translation unit.

Consequently Base<hello> will be different types in different translation units and so if you include child.hh in more than one translation unit you will violate ODR on Child's definition and therefore your program will have undefined behavior.

The warning is telling you that. The use of "anonymous namespace" is not good. It should say something like "whose type refers to an entity with internal linkage". But otherwise it is exactly stating the problem.

This happens only if you use a pointer or reference to the object as template argument. If you take just the value of a variable, then it doesn't matter whether that variable has internal or external linkage since the type Base<...> will be determined by the value of the variable, not the identity of the variable.


I don't know why you need the template argument to be a pointer here, but if you really need it and you actually want to include the .hh file in multiple .cc files (directly or indirectly), then you need to give hello external linkage. Then you will have the problem that an external linkage variable may only be defined in one translation unit, which you can circumvent by making the variable inline:

inline constexpr char hello[] = "Hello world!";

inline implies external linkage (even if the variable is const) and constexpr implies const.


If you only need the value of the string, not the identity of the variable, then since C++20 you can pass a std::array<char, N> as template argument:

#include<array>
#include<concepts>

/*...*/

template<std::array s>
requires std::same_as<typename decltype(s)::value_type, char>
class Base
{
public:
  void print()
  {
    std::cout << s.data() << std::endl;
  }
};

/*...*/

constexpr auto hello = std::to_array("Hello world!");

With this linkage becomes irrelevant. Writing Base<std::to_array("Hello world!")> is also possible.

Before C++20 there isn't really any nice way to pass a string by-value as template argument.

Even in C++20 you might want to consider writing your own std::array-like class specifically for holding strings. Such a class needs to satisfy the requirements for a structural type. (std::string does not satisfy them.) On the other hand you might want to widen the allowed template parameter types by using the std::range concept and std::range_iter_t instead of std::array to allow any kind of range that could represent a string. If you don't want any restrictions just auto also works.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • I have a similiar issue here but I am a bit lost. At first I tried to pass my own struct type via template parameter with a constexpr definition of this struct. But this is not possible in C++14 but may be there in future versions of C++ (I guess the same example for c++20 you showed). There is also a post about this: https://stackoverflow.com/questions/15896579/why-cant-a-struct-be-passed-as-value-as-template-non-type-parameter – NetoBF Jun 22 '23 at 15:29
  • So I used a reference. I got the same error. Then I tried to use the inline constexpr statement, but this is only available from C++17 up. So if you use C++14, what can you do to solve this? – NetoBF Jun 22 '23 at 15:31
  • 1
    @NetoBF You can't pass classes by value as template arguments before C++20. You can pass pointers/references to them, but then the template argument is determined by the identity of the pointed-to/referenced object, which must have static storage duration. In C++14 you can declare the variable with `extern` and then define it in one `.cpp` file. If you have some specific problem with that I recommend asking another question. It will likely be closed as duplicate depending on your exact problem, but that is ok. Mention your limitations and reference the questions you have already looked at. – user17732522 Jun 22 '23 at 18:18
6

If child.hh is included in multiple TUs, it would violate ODR and lead to undefined behavior NDR as explained here.

A new bug for better wording of the warning has been submitted here:

Bug 106141 - Better wording for warning: ‘Child’ has a base ‘Base<(& hello)>’ whose type uses the anonymous namespace [-Wsubobject-linkage]


There is also an old bug submitted as:

Bug 86491 - bogus and unsuppressible warning: 'YYY' has a base 'ZZZ' whose type uses the anonymous namespace.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • I have not read through the test case in the linked bug report, but the warning is appropriate for the code in this question. See my answer. – user17732522 Jun 29 '22 at 20:52
  • @user17732522 Agree, a new bug report has been submitted [here](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106141) for this particular example. I've also mentioned the same in my updated answer. – Jason Jun 30 '22 at 04:08