user743382
user743382

Reputation:

Must template argument functions be treated as potentially constexpr?

Consider this program:

template <int F(), int N = F()>
void f() { }
constexpr int g() { return 1; }
int main() { f<g>(); }

Is this valid? Are compilers required to see at template definition time, that F could potentially refer to a constexpr function, and therefore the default argument for N could be valid?

gcc and clang accept this, but Intel1 rejects the template function at template definition time because F() is not a constant expression. Intel does accept f<g, g()>() if the default argument is removed, so clearly it understands that g() is usable in a constant expression in general.

It's not clear to me what the standard says. It's clear that (C++11 [expr.const]p2)

an invocation of a function other than a constexpr constructor for a literal class or a constexpr function

renders an expression non-constant, but it's not clear to me whether that applies here. At template definition time, it certainly does seem to apply, since F is not declared to be a constexpr function, but at the same time, errors at template definition time are supposed to be diagnosed only if there is no possible valid instantiation of the template, and there does appear to be a valid instantiation here.

I can see the arguments for both answers, so I'm getting confused. Is there a definitive answer to this question?

1. Re-testing with the current release of Intel's compiler shows that it works just fine, so presumably the Intel developers considered it a bug and have since fixed it. This is a huge hint that the code is meant to be valid. It would still be nice to get a conclusive answer based on the standard, though.

Upvotes: 31

Views: 1822

Answers (1)

Filip Ros&#233;en
Filip Ros&#233;en

Reputation: 63902

Introduction

template<int F(), int N = F()> void func ();

In this answer we will go through the relevant sections of the International Standard, step by step, to prove that the above snippet is well-formed.


What does the International Standard (N3337) say?

The Standardese

14.1p9 Template parameters [temp.param]

A default template-argument is a template-argument (14.3) specified after = in a template-parameter. [...]

14.3p6 Template arguments [temp.arg]

If the use of a template-argument gives rise to an ill-formed construct in the instantiation of a template specialization, the program is ill-formed.

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:

  • for a non-type template-parameter of integral or enumeration type, a converted constant expression (5.19) of the type of the template-parameter; or
  • the name of a non-type template-parameter; or
  • a constant expression (5.19) that designates the address of an object [...]; or
  • a constant expression that evaluates to a null pointer value (4.10); or
  • a constant expression that evaluates to a null member pointer value (4.11); or
  • a pointer to member expressed as described in 5.3.1

5.19p3 Constant expressions [expr.const]

A literal constant expression is a prvalue core constant expression of literal type, but not pointer type. An integral constant expression is a literal constant expression of integral or unscoped enumeration type. A converted constant expression of type T is a literal constant expression, implicitly converted to the type T, [...]

8.3.6p3 Default arguments [dcl.fct.default]

A default argument shall be specified only in the parameter-declaration-clause of a function declaration or in a template-parameter (14.1); in the latter case, the initializer-clause shall be an assignment-expression.


The Verdict

The above sections makes us come to the following conclusions:

  • A default template-argument is a template-argument, and;
  • when instantiating a template, all template-arguments must be usable in the context where they appear, and;
  • every template-argument for a non-type, non-template template-parameter that appears in a program must be a literal constant expression, and;
  • the default-argument for a template-parameter shall be an assignment-expression.

The Explanation

template<int F(), int N = F()>
void func ();

constexpr int (*F)() = <some_initializer>;                    // (A)
constexpr int N      = <explicit_template_argument> OR <F()>  // (B)

The snippet above can be used as a mental helper to ease reasoning about what the template-parameters will be equivalent to, given a set of template-arguments.

To see whether (B) is valid or not, where an explicit template-argument is not given for N, we must evaluate (A) - and the evaluation of (A) might yield a value for F that is usable in the constant-expression required by (B).

With that said; Yes, the template is legal C++11.

Legal

constexpr int g () { ... }

// func<&g> 
constexpr int (*F)() = &g;  // ok
constexpr int N      = F(); // ok

Ill-formed

          int f () { ... }

// func<&f>
constexpr int (*F)() = &f;  // ok
constexpr int N      = F(); // ill-formed, not a constant-expression

Bonus

The same set of rules apply to the following template;

template<int X, int N = 100/X>
void gunc ();

gunc<0> (); // ill-formed, `100/0` is not mathematically defined,
            //              and is therefore not a constant-expression


For the language-lawyer

And this, pointless use of a default template-argument, is actually legal since F() might be a constant-expression.

F() can however not be a converted constant-expression to give N a value, but this doesn't happen until (if ever) the default argument is actually used.

template<void F(), int N = F()>
void hunc ();

void f ();

hunc<&f, 10> (); // legal
hunc<&f    > (); // ill-formed

Upvotes: 8

Related Questions