Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specification rule of "definition of a static data member is in the scope of its class" with a templated base class

The C++ specification (e.g. C++17 [class.static] §2) says:

The definition of a static data member is in the scope of its class.

With an example (taken from C++14 spec):

int g();
struct X {
  static int g();
};
struct Y : X {
  static int i;
};
int Y::i = g();                 // equivalent to Y::g();

The exact wording and example in the specification have changed over the years, for some unknown reasons, C++11 and C++14 have a quite similar example as the above, C++17 and C++20 preferred an example initializing from a static data member. The C++20 wording is also much more concise. But it seems that there is no actual change in this rule.


It seems that this rule doesn't work well for inheritance of a templated class:

template<int VALUE>
struct base {
    static int foo() { return VALUE; }
    static constexpr int z = -1;
};

template<int VALUE>
struct foobar: base<VALUE> {
    static int x1;
    static int x2;
    static int y1;
    static int y2;
};

int foo() { return 42; }

int z = 999;

template<int VALUE>
int foobar<VALUE>::x1 = foobar::foo();

template<int VALUE>
int foobar<VALUE>::x2 = foo();

template<int VALUE>
int foobar<VALUE>::y1 = foobar::z;

template<int VALUE>
int foobar<VALUE>::y2 = z;

int main() {
    std::cout << foobar<10>::x1 << ' ' << foobar<10>::x2 << ' '
              << foobar<10>::y1 << ' ' << foobar<10>::y2;
}

Output:

10 42 -1 999

Both GCC 10.2 and Clang 5.0.0 agree on the above (surprising?) output.

Is it the right output, or is it a bug in both compilers?

The expected output, expecting that calling foo(), in the context of a static member initialization, would behave as calling foobar::foo(), etc., should be:

10 10 -1 -1

Note: the behavior for inheritance as well as for a template class without inheritance and a template base with a non-template derived, all seems to work correctly.

like image 989
Amir Kirsh Avatar asked Jan 06 '21 14:01

Amir Kirsh


2 Answers

GCC and Clang are both correct.

Particularly, from [temp.dep]/3 [emphasis mine]:

In the definition of a class or class template, the scope of a dependent base class is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member. [ Example:

typedef double A;
template<class T> class B {
  typedef int A;
};
template<class T> struct X : B<T> {
  A a;              // a has type double
};

The type name A in the definition of X<T> binds to the typedef name defined in the global namespace scope, not to the typedef name defined in the base class B<T>.  — end example ] [...]

In the definitions the static data members x1 and y1 of the of derived class template foobar:

template<int VALUE>
int foobar<VALUE>::x1 = foobar::foo();

template<int VALUE>
int foobar<VALUE>::y1 = foobar::z;

you are using the injected-class-name ([temp.local]/3) to access the dependent names ([temp.res]/9) foo and z from the base class template, whereas the unqualified names used in the definitions of the static data members x2 and y2 will not examine the scope of the dependent base class.


Note that we may bring in the names of the dependent base class into the scope of the derived class, redefining foobar as

template<int VALUE>
struct foobar: base<VALUE> {
    using base<VALUE>::foo;
    using base<VALUE>::z;
    static int x1;
    static int x2;
    static int y1;
    static int y2;
};

in which case the output of your program will be

10 10 -1 -1
like image 81
dfrib Avatar answered Nov 14 '22 16:11

dfrib


This is just another case of the binding rules for non-depended names, which are looked up and bound at the point of template definition. (Not to be confused with ADL! Even while ADL would help with the call to foo() if it had any parameter.)
For the full details please read here: https://en.cppreference.com/w/cpp/language/dependent_name

Similar artifact happens with calling a function from template base class:

template <typename T>
struct A
{
    void f(){}
};

template <typename T>
struct B : A<T>
{
    void g()
    {
        A<T>::f();     // of course works
        this->f();     // this-> makes it depended, so it's bound only on instantiation time

        f();           // compilation error, no f() found

        using A<T>::f; // make the name available (and depended on the template)
        f();           // now it works
    }
};

https://gcc.godbolt.org/z/39KGvM

like image 3
Yehezkel B. Avatar answered Nov 14 '22 16:11

Yehezkel B.