Does `using` in a template class force instantiation? - c++

This question is on how to use global variable initialization to achieve some side effect before main() is run (e.g., factory class registration before main()). This can be tricky when template is involved. Consider the following piece of code:
template <class T>
class A {
public:
static bool flag;
static bool do_something() {
// Side effect: do something here.
}
template <bool&>
struct Dummy {};
// Important: Do not delete.
// This line forces the instantiation and initialization of `flag`.
using DummyType = Dummy<flag>;
};
template <class T>
bool A<T>::flag = A<T>::do_something();
// A<int>::flag is instantiated and.
// A<int>::do_something will be called before main to initialize A<int>::flag.
class B : A<int> {};
If we remove the using, the behavior will be different:
template <class T>
class A {
public:
static bool flag;
static bool do_something() {
// Side effect: do something here.
}
// The `using` dummy part is missing.
};
template <class T>
bool A<T>::flag = A<T>::do_something();
// A<int>::flag is not instantiated.
// A<int>::do_something will not be called.
class B : A<int> {};
My question: is the using trick here a compiler specific thing, or is it backed up by the C++ standard? I did some search and found some relevant information about the standard 14.7.1, but still not sure about the rules on using as it's not mentioned in 14.7.1 (my source is this one, not sure if it should be considered as the ground truth). Also, the using Dummy seems somewhat hacky to me. Are there any other workarounds to make it more elegant? The goal is to resolve the instantiation within class A itself so that the side effect related code segments remain private.

Thanks to new wording in C++20, it is possible to give a definitive answer to your question. In the comments, Igor Tandetnik mentioned the relevant paragraph in C++17, namely [temp.inst]/3. In C++20, it is [temp.inst]/4, which has some new relevant wording that I have bolded:
Unless a member of a class template or a member template is a declared specialization, the specialization of
the member is implicitly instantiated when the specialization is referenced in a context that requires the
member definition to exist or if the existence of the definition of the member affects the semantics of the
program; in particular, the initialization (and any associated side effects) of a static data member does not
occur unless the static data member is itself used in a way that requires the definition of the static data
member to exist.
Later in the same section, paragraph 8:
The existence of a definition of a variable or function is considered to affect the semantics of the program if the variable or function is needed for constant evaluation by an expression (7.7), even if constant evaluation of the expression is not required or if constant expression evaluation does not use the definition.
In short, if the alias-declaration using DummyType = Dummy<flag>; is instantiated, then the static data member flag is needed for constant evaluation, therefore it is instantiated.
Informally, "needed for constant evaluation" refers to certain situations where a variable or function is not odr-used, but the way it is used in constant evaluation still requires its definition.
Formally, the definition of "needed for constant evaluation" is given in [expr.const]/15:
An expression or conversion is potentially constant evaluated if it is:
a manifestly constant-evaluated expression,
a potentially-evaluated expression (6.3),
an immediate subexpression of a braced-init-list,
an expression of the form & cast-expression that occurs within a templated entity, or
a subexpression of one of the above that is not a subexpression of a nested unevaluated operand.
A function or variable is needed for constant evaluation if it is:
a constexpr function that is named by an expression (6.3) that is potentially constant evaluated, or
a variable whose name appears as a potentially constant evaluated expression that is either a constexpr variable or is of non-volatile const-qualified integral type or of reference type.
"Manifestly constant-evaluated" is defined in p14 of that section:
An expression or conversion is manifestly constant-evaluated if it is: [...] a constant-expression, or [...] [Note 7: A manifestly constant-evaluated expression is evaluated even in an unevaluated operand. — end note]
"constant-expression" is a grammar production that is used in various contexts to denote a conditional-expression that must be a constant expression. We can check the grammar for simple-template-id (which is what Dummy<flag> is):
simple-template-id :
     template-name < template-argument-list(opt) >
[...]
template-argument-list :
     template-argument ...(opt)
     template-argument-list , template-argument ...(opt)
template-argument :
     constant-expression
     type-id
     id-expression
Here, id-expression refers to a template template parameter. In using DummyType = Dummy<flag>;, flag is an argument for a non-type template parameter, so it is a constant-expression.
Since the expression flag is a manifestly constant-evaluated expression, it is a potentially constant-evaluated expression. The variable flag's name "appears as a potentially constant evaluated expression" and it's a variable of reference type, so it is needed for constant evaluation.
Note that the concept of "needed for constant evaluation" was introduced by P0589 to resolve CWG 1581. This was voted as a DR, so its resolution is retroactive. The concepts of "needed for constant evaluation" and "manifestly constant-evaluated" always needed to be in C++; their absence led to issues like the one in this question.

Related

What justifies the lvalue category of unevaluated non-static data members in C++?

Both gcc and clang accept the following code, and I'm trying to figure out why.
// c++ -std=c++20 -Wall -c test.cc
#include <concepts>
struct X {
int i;
};
// This is clearly required by the language spec:
static_assert(std::same_as<decltype(X::i), int>);
// This seems more arbitrary:
static_assert(std::same_as<decltype((X::i)), int&>);
The first static_assert line makes sense according to [dcl.type.decltype]:
otherwise, if E is an unparenthesized id-expression or an unparenthesized class member access ([expr.ref]), decltype(E) is the type of the entity named by E. If there is no such entity, or if E names a set of overloaded functions, the program is ill-formed;
- https://timsong-cpp.github.io/cppwp/n4861/dcl.type.decltype#1.3
X::i is a valid id-expression in an unevaluated context, so its decltype should be the declared type of i in X.
The second static_assert has me stumped. There's only one clause in [dcl.type.decltype] in which a parenthesized expression yields an lvalue reference: it must be that both compilers consider X::i to be an expression of lvalue category. But I can't find any support for this in the language spec.
Obviously if i were a static data member then X::i would be an lvalue. But for a non-static member, the only hint I can find is some non-normative language in [expr.context]:
In some contexts, unevaluated operands appear ([expr.prim.req], [expr.typeid], [expr.sizeof], [expr.unary.noexcept], [dcl.type.simple], [temp.pre], [temp.concept]).
An unevaluated operand is not evaluated.
[ Note: In an unevaluated operand, a non-static class member may be named ([expr.prim.id]) and naming of objects or functions does not, by itself, require that a definition be provided ([basic.def.odr]).
An unevaluated operand is considered a full-expression.
— end note
]
- https://timsong-cpp.github.io/cppwp/n4861/expr.prop#expr.context-1
This suggests decltype((X::i)) is a valid type, but the definition of full-expression doesn't say anything about value categories. I don't see what justifies int& any more than int (or int&&). I mean an lvalue is a glvalue, and a glvalue is "an expression whose evaluation determines the identity of an object." How can an expression like X::i--which can't be evaluated at all, let alone determine the identity an object--be considered a glvalue?
Are gcc and clang right to accept this code, and if so what part of the language specification supports it?
remark:
StoryTeller - Unslander Monica's answer makes even more sense in light of the fact that sizeof(X::i) is allowed and that decltype((X::i + 42)) is a prvalue.
How can an expression like X::i--which can't be evaluated at all, let alone determine the identity an object--be considered a glvalue?
That's not entirely true. That expression can be evaluated (following a suitable transformation in the right context).
[class.mfct.non-static]
3 When an id-expression that is not part of a class member access syntax and not used to form a pointer to member ([expr.unary.op]) is used in a member of class X in a context where this can be used, if name lookup resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression using (*this) as the postfix-expression to the left of the . operator.
So we can have something like
struct X {
int i;
auto foo() const { return X::i; }
};
Where X::i is transformed into (*this).X::i. Which is explicitly allowed on account of
[expr.prim.id]
2 An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
as part of a class member access in which the object expression refers to the member's class or a class derived from that class, or ...
And that meaning of (*this).X::i is always an lvalue denoting the class member i.
So you see, in the contexts where X::i can be evaluated, it always produces an lvalue. So decltype((X::i)) in those context would need to be an lvalue reference type. And while outside of class scope X::i cannot generally be used in an expression, it's still not entirely arbitrary to say its value category is an lvalue. We are just expanding the region of definition a bit (which doesn't contradict the standard, since the standard doesn't define it).
How can an expression like `X::i--which can't be evaluated at all, let alone determine the identity an object--be considered a glvalue?
Ignoring the misuse of «result», it is [expr.prim.id.qual]/2:
A nested-name-specifier that denotes a class, optionally followed by the keyword template ([temp.names]), and then followed by the name of a member of either that class ([class.mem]) or one of its base classes, is a qualified-id; ... The result is an lvalue if the member is a static member function or a data member and a prvalue otherwise.
The value category of an expression is not determined by the «definition» in [basic.lval], like «an expression whose evaluation determines the identity of an object», but specified for each kind of expression explicitly.
The root of your question seems to be the difference between decltype(X::i) and decltype((X::i)). Why does (X::i) yield a int&? See:
https://timsong-cpp.github.io/cppwp/n4861/dcl.type.decltype#1.5
otherwise, if E is an lvalue, decltype(E) is T&, where T is the type of E;
However, the key point here is that the fact that it yields a T& doesn't really matter:
https://timsong-cpp.github.io/cppwp/n4861/expr.type#1
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression. [ Note: Before the lifetime of the reference has started or after it has ended, the behavior is undefined (see [basic.life]). — end note ]
What "justifies" it then? Well, when doing decltype(X::i) we're concerned primarily with what the type of X::i is and not its value category or its properties when treated as an expression. However, (X::i) is there if we do care.

Is a function definition required to be instantiated when there is no need to check for narrowing?

Consider the following program:
template<typename T>
constexpr int f()
{
T{}.i; // error if instantiated with [T = double]
return 42;
}
constexpr void g(char);
using U = decltype( g( {f<double>()} ) );
To my understanding, the last line is an error because the call to f<double>() is within a brace initializer, and even though f<T> returns an int, the value of the returned int is needed to decide if it can be narrowed to a char as expected by g. This requires the definition of f to be instantiated with double, which causes an error. Both gcc and clang reject this code.
However, if the definition of g is changed to accept an int parameter:
constexpr void g(int);
then it seems that there is no need to instantiate the definition of f, since the narrowing conversion must succeed. Indeed, gcc accepts this, but clang still instantiates f with double and rejects the code. Additionally, if f is only declared, but not defined, clang accepts the code, which implies that the definition is not needed, and shouldn't be instantiated.
Is my reasoning correct, and this is a clang bug, or is instantiation required, and this is actually a gcc bug?
This is CWG #1581 I think, resolved by P0859.
temp.inst/5 says:
Unless a function template specialization is a declared specialization, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist or if the existence of the definition affects the semantics of the program.
Does the existence affect the semantics of the program?
temp.inst/8 says:
The existence of a definition of a variable or function is considered to affect the semantics of the program if the variable or function is needed for constant evaluation by an expression ([expr.const]), even if constant evaluation of the expression is not required or if constant expression evaluation does not use the definition.
Is it needed for constant evaluation by an expression?
expr.const/15.6-7 says:
A function or variable is needed for constant evaluation if it is:
a constexpr function that is named by an expression that is potentially constant evaluated, or
a variable whose name appears as a potentially constant evaluated expression that is either a constexpr variable or is of non-volatile const-qualified integral type or of reference type.
Is it named by an expression that is potentially constant evaluated?
expr.const/15.1-15.5 says:
An expression or conversion is potentially constant evaluated if it is:
a manifestly constant-evaluated expression,
a potentially-evaluated expression,
an immediate subexpression of a braced-init-list,
an expression of the form & cast-expression that occurs within a templated entity, or
a subexpression of one of the above that is not a subexpression of a nested unevaluated operand.
It is an immediate subexpression of a braced-init-list.

Template non-type arguments for reference type and odr-used

Is the variable v in the sample code below odr-used?
extern void* v;
template<void*&>
void f() {}
int main()
{
f<v>();
}
I found this pattern in Boost ML.
cf. http://lists.boost.org/Archives/boost/2011/04/180082.php
It says that the boost::enabler is never defined, but clang rejects it as a linkage error if -g option is provided.
cf. http://melpon.org/wandbox/permlink/nF45k7un3rFb175z
The sample code above is reduced version of the Boost ML's code and clang rejects it too.
cf. http://melpon.org/wandbox/permlink/ZwxaygXgUhbi1Cbr
I think (but I am not sure) that template non-type arguments for reference type are odr-used even if they are not referred in their template body so the Boost ML's pattern is ill-formed.
Is my understanding correct?
I believe v is odr-used. f<v> is a template-id (14.2) whose template-argument is an id-expression (5.1.1) - a form of expression. It's clearly not an unevaluated operand (it doesn't appear as an operand of typeid, sizeof, noexcept or decltype), so it's potentially evaluated per 3.2/2:
3.2/2 An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof...
At which point, we have
3.2/3 A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless [a condition that doesn't appear to apply here as no lvalue-to-rvalue conversion is applied].
[basic.def.odr]/3:
A variable x whose name appears as a potentially-evaluated expression
ex is odr-used by ex unless applying the lvalue-to-rvalue conversion
(4.1) to x yields a constant expression (5.19) [..]
Unfortunately, applying the l-t-r conversion to v at this point would not yield a constant expression - [expr.const]/2:
A conditional-expression e is a core constant expression unless
the evaluation of e, following the rules of the abstract machine
(1.9), would evaluate one of the following expressions: [..]
— an
lvalue-to-rvalue conversion (4.1) unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding
initialization, initialized with a constant expression [..], or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers
to a non-mutable sub-object of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
However, though the implementation suggested by Matt isn't correct, the idea certainly is. A simple way of using this approach is demonstrated in this answer, using a helper template. In your case, try
template <bool cond, int id=0>
using distinct_enable_if =
typename std::enable_if<cond, std::integral_constant<int,id>*>::type;
class test
{
public:
template< class... T,
distinct_enable_if<sizeof...(T) == 10> = nullptr>
test( T&&... ) {}
template< class T,
distinct_enable_if<std::is_arithmetic<T>{}> = nullptr>
operator T() const { return T{}; }
/* Note the additional template argument:
It ensures that the template parameter lists are not identical,
and the ODR isn't violated */
template< class T,
distinct_enable_if<std::is_pointer<T>{}, 1> = nullptr>
operator T() const { return T{}; }
};
Demo.

Reference as a non-type template argument

The example below attempts to use a variable of reference type as an argument for a non-type template parameter (itself of reference type). Clang, GCC and VC++ all reject it. But why? I can't seem to find anything in the standard that makes it illegal.
int obj = 42;
int& ref = obj;
template <int& param> class X {};
int main()
{
X<obj> x1; // OK
X<ref> x2; // error
}
Live example
CLang says:
source_file.cpp:9:7: error: non-type template argument of reference type 'int &' is not an object
Others complain in similar ways.
From the standard (all quotes from C++11; C++14 doesn't appear to have significant changes in relevant parts):
14.3.2/1 A template-argument for a non-type, non-template template-parameter shall be one of:
...
a constant expression (5.19) that designates the address of an object with static storage duration and external or internal linkage ... expressed (ignoring parentheses) as & id-expression, except that the & ... shall be omitted if the corresponding template-parameter is a reference
...
Now what's a constant expression:
5.19/2 A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2)...
...
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization, initialized with a constant expression
...
As far as I can tell, ref in X<ref> is an id-expression that refers to a variable of reference type. This variable has a preceding initialization, initialized with the expression obj. I believe obj is a constant expression, and anyway if it isn't, then X<obj> shouldn't compile either.
So what am I missing?
Which clause in the standard renders X<ref> invalid, while X<obj> is valid?
Introduction
It is correct saying that the name of a reference is an id-expression, though; the id-expression doesn't refer to whatever the reference is referencing, but the reference itself.
int a = 0;
int& ref = a; // "ref" is an id-expression, referring to `ref` - not `a`
The Standard (N4140)
You are quoting the relevant sections of the standard in your post, but you left out the most important part (emphasize mine):
14.3.2p1 Template non-type arguments [temp.arg.nontype]
A template-argument for a non-type, non-template template-parameter shall be one of:
...
a constant expression (5.19) that designates the address of a complete object with static sturage duration and external or internal linkage or a function with external or internal linkage, including function templates and function
template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, where id-expression is the name of an object or function, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; ...
Note: In earlier drafts "where id-expression is the name of an object or function" isn't present; it was addressed by DR 1570 - which undoubtedly makes the intent more clear.
A variable of reference type is not an object?
You are absolutely correct; the reference itself has reference type, and can merely act as an object when part of an expression.
5p5 Expressions [expr]
If an expression initially has the type "reference to T" (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
Elaboration
It is very important to note that the constant expression ("that designates the address of a complete object...") must be one of &id-expression, or id-expression.
Even though a constant-expression, that isn't just an id-expression, might refer to an object with static storage duration, we cannot use it to "initialize" a template-parameter of reference- or pointer type.
Example Snippet
template<int&>
struct A { };
int a = 0;
constexpr int& b = (0, a); // ok, constant-expression
A<(0, a)> c = {}; // ill-formed, `(0, a)` is not an id-expression
Note: This is also a reason behind the fact that we cannot use string-literals as template-arguments; they are not id-expressions.

`static constexpr auto` data-member initialized with unnamed enum

I was working on a C++11 project solely using clang++-3.4, and decided to compile using g++-4.8.2 in case there were any discrepancies in the errors produced. It turned out that g++ rejects some code that clang++ accepts. I have reduced the problem to the MWE given below.
enum { a };
template <class T>
struct foo
{
static constexpr auto value = a;
};
int main()
{
static constexpr auto r = foo<int>::value;
}
foo.cpp:5:23: error: ‘const<anonymous enum> foo<int>::value’, declared using anonymous type, is used but never defined [-fpermissive]
static const auto value = A;
I would like some help answering the following two questions:
Which compiler is correct in its interpretation of the standard? I am assuming that one compiler is right in either accepting or rejecting the code, and the other is wrong.
How can I work around this issue? I can't name the anonymous enum, because it is from a third-party library (in my case, the enums were Eigen::RowMajor and Eigen::ColMajor).
Who's to blame?
GCC is inaccurately rejecting your snippet, it is legal according to the C++11 Standard (N3337). Quotations with proof and explanation is located the end of this post.
workaround (A) - add the missing definition
template <class T>
struct foo {
static constexpr auto value = a;
typedef decltype(a) value_type;
};
template<class T>
constexpr typename foo<T>::value_type foo<T>::value;
workaround (B) - use the underlying-type of the enumeration as placeholder
#include <type_traits>
template <class T>
struct foo {
static const std::underlying_type<decltype(a)>::type value = a;
};
What does the Standard say? (N3337)
As stated, the snippet is legal C++11, as can be read in the following quoted sections.
When can we use a type without linkage?
[basic.link]p8 has detailed wording that describes when a type is "without linkage", and it states that an unnamed enumeration count as such type.
[basic.link]p8 also explicitly states three contexts where such a type cannot be used, but not one of the contexts apply to our usage, so we are safe.
A type without linkage shall not be used as the type of a variable or function with external linkage unless
the entity has C language linkage (7.5), or
the entity is declared within an unnamed namespace (7.3.1), or
the entity is not odr-used (3.2) or is defined in the same translation unit
Are you sure we can use auto in such context?
Yes, and this can be proven by the following quote:
7.1.6.4p auto specifier [dcl.spec.auto]
A auto type-specifier can also be used in declaring a variable in the condition of a selection statement (6.4) or an iteration statement (6.5), in the type-specifier-seq in the new-type-id or type-id of a new-expression (5.3.4), in a for-range-declaration, and in declaring a static data member with a brace-or-equal-initializer that appears within the member-specification of a class definition (9.4.2).
Which compiler is correct in its interpretation of the standard?
gcc is incorrect. §9.4.2/3:
A static data member of literal type can be declared in the class
definition with the constexpr specifier; if so, its declaration shall
specify a brace-or-equal-initializer in which every initializer-clause
that is an assignment-expression is a constant expression. The member
shall still be defined in a namespace scope if it is odr-used (3.2) in
the program and the namespace scope definition shall not contain an
initializer.
And the name is not odr-used as per §3.2:
A variable whose name appears as a potentially-evaluated expression is
odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue
conversion (4.1) is immediately applied.
This is indeed the case: It does satisfy the requirements for appearing in a constant expression and the lvalue-to-rvalue conversion is immediately applied (it is used as an initializer for an object). So GCC's rejection is incorrect.
A possible workaround is to define the member (but without a placeholder type). This definition is sufficient for both Clang and GCC:
template< typename T >
constexpr decltype(a) foo<T>::value;
Workaround with decltype:
enum { a };
template <class T>
struct foo
{
static constexpr auto value = a;
};
template <class T>
constexpr decltype(a) foo<T>::value;
int main()
{
static constexpr auto r = foo<int>::value;
}