How should a Variable Template be referred to in C++14 when declared at Class scope?

1.1k views Asked by At

For example:

class example{
    public:
        template <class T> static constexpr T var = T(1.5);
};

int main(){

    int a = example::var<int>;

    example obj;
    int b = obj.var<int>;

    return 0;
}

GCC produces error for both: 'example::var<T>' is not a function template and 'var' is not a member template function

Clang compiles correctly the first one but produces an error for the second: cannot refer to member 'var' in 'example' with '.'

According to the C++14 Standard (ISO/IEC 14882:2014):

Section 14, Paragraph 1.

A variable template at class scope is a static data member template.

Section 9.4, Paragraph 2.

A static member s of class X may be referred to using the qualified-id expression X::s; it is not necessary to use the class member access syntax (5.2.5) to refer to a static member. A static member may be referred to using the class member access syntax, in which case the object expression is evaluated.

Therefore, IMHO, a Variable Template at Class scope (i.e., a Static Data Member Template) could be referred to in both ways. Could it be a bug in the compilers?

The only thing I found to try to justify this behaviour is this sentence in Section 9.4.2, Paragraph 1:

A static data member is not part of the subobjects of a class.

However, the two aforementioned paragraphs are still valid. Besides, I tried the same example referring to other static members such as a variable, a function and a function template, and all of them compile successfully in both GCC and Clang.

class example{
    public:
        static int constexpr variable = 1;
        void static function(){ return; }
        template <class T> void static function_template(){ return; }
};

int main(){

    example obj;

    int a = obj.variable;
    int b = example::variable;

    obj.function();
    example::function();

    obj.function_template<int>();
    example::function_template<int>();

   return 0;
}

Thanks in Advance.

Note 1: compiler versions are clang 3.7.0 and gcc 5.2.1.

Note 2: the keyword static is required: Variable template at class scope

Note 3: since I want to initialize the variable template, the keyword constexpr is also required because in my actual code I will instantiate it with float, double and long double (see C++14 Standard (ISO/IEC 14882:2014), Section 9.4.2, Paragraph 3).

Note 4: actual "definitions" of these static data members outside the class (i.e., template <class T> constexpr T example::var;) are not needed in this example cases. I also tried though, but it makes no difference.

1

There are 1 answers

3
DeMayo On BEST ANSWER

I copied your first code into Visual Studio 2015 (it compiled successfully). I added a few outputs via std::cout, where I found using b gives a compiler error: uninitialized local variable 'b' used. a on the other hand, was successfully printed when b was not used. So it would seem c++ is a bit finicky about accessing template static members as you said, requiring you to reference it by its complete, qualified name.

Perhaps more curious, are the following lines:

std::cout << example::var<int> << "a\n";

The above line works as expected, outputting 1.5 truncated to 1 and an 'a' with a new line. Nothing to write home about.

std::cout << obj.var<int> << "b\n";

Now here's where it gets interesting... Not only does the above line not print out a value for obj.var<int>, the 'b'\n never gets printed either. I even tested against the std::cout's good() fail() and bad() functions, none of which reported that anything was wrong (and further uses of std::cout were executed successfully outputted).

Another oddity I found was that auto x = obj.var is legal, and come to find, x is of type example. Now, doing this with a global template variable results in a compiler error (as I expected the first one to as well):

template<typename T> constexpr T ex = 1.5;
auto x = ex // compiler error: argument list for variable template "ex" is missing

Additionally, I found that access var through another template static function was successful, further implying that member selection just doesn't work in this case

class example
{
public:
    template <class T> static constexpr T var = T(1.5);
    template <typename T> static void thing()
    {
        std::cout << var<T> << '\n';          // works
        std::cout << example::var<T> << '\n'; // also works
    }
};

Now, as far as the standard goes, I'm inclined to believe their phrasing is just a bit ... pedantic. The sections you've quoted from the standard:

it is not necessary to use the class member access syntax (5.2.5) to refer to a static member.

and

A variable template at class scope is a static data member template.

would seem to imply that this would work. I believe the point where these quotes don't apply in this case is the technicality that a template (of anything) doesn't really "exist" until it is instantiated in a compilation unit (i.e. why the code for templates is often included in the header file itself).

Because of this, although the template variable can be a member of a class, its instantiations aren't ... for some reason ... and therefore require the scope resolution operator as opposed to the member selection operator.

But IMO, accessing static data via the member selection operator is bad practice, as static data and functions are not actually part of a given object. Accessing static data the same way as non-static data can cause relatively-innocent looking code to actually be flawed logic. For instance if for some reason you had a non-const static member called something, you could write example_object.something = 42, not expecting anything to change for all other instantiations of that class throughout your program (the same issues as global variables, really). Because of this (and the fact that member access syntax apparently doesn't work for template static member variables anyway), I recommend always using scope resolution to access/modify static content from outside of the class. example_class::something = 42 is a lot more clear that we're changing something for all instances of example_class. In fact, some more-modern languages like C# require you to access static data via the class name unless you're inside said class.

Given that several compilers are erroring for different parts of this little example program, I'd be willing to bet it's not covered very well in the standard (and probably not used very often in practice), and the compilers just handle it differently (another reason to avoid it).

tl;dr

Apparently while member selection syntax works for static member variables, it doesn't work for template static member variables (though the compiler doesn't seem to complain). However, the scope resolution syntax does work, and IMO should be preferred anyway.