Constexpr Function Parameters as Template Arguments

constexpr function parameters as template arguments

You tell the compiler, that addFunc would be a constexpr. But it depents on parameters, that are not constexpr itself, so the compiler already chokes on that. Marking them const only means you are not going to modify them in the function body, and the specific calls you make to the function are not considered at this point.

There is a way you can make the compiler understand you are only going to pass compile time constants to addFunc: Make the parameters a template parameters itself:

template <int x, int y>
constexpr int addFunc() {
return add<x,y>::ret;
}

Then call as

cout << addFunc<1,2>() << endl;

Not using constexpr in c++ template arguments

Your get_dimension function should be constexpr and, if that is the case, you can have the following:

constexpr unsigned int Dimension = get_dimension(...);

Example

Let's say you have the following simplified class:

template <int v>
class Foo {
public:
constexpr Foo()
: v_(v)
{}

private:
int v_;
};

and then the following:

int v = get();
using FooInt = Foo<v>;

where get function is defined as follows:

int get() {
return 1;
}

You will get the same as error as you are getting in your example.

Therefore, the solution would be to mark get function constexpr and make the v value also constexpr like:

constexpr int get() {
return 1;
}

constexpr int v = get();
using FooInt = Foo<v>;

Take a look at the demo

UPDATE

In order to be able to use templates, compiler needs to know template parameters at compile time, and therefore, if Dimension is not a constexpr (which declares that it is possible to evaluate the value of the variable at compile time) variable, it cannot be used as template parameter.

when to use template non-type classes or plain arguments in constexpr functions

The short version: Use non-type template parameter to set non-type template arguments (more general everywhere, where you need a constant expression) and normal arguments for everything else.

The thing about constexpr functions you always have to keep in mind is that they can also be called at runtime. So every normal argument is not necessarily a constant expression. Hence you cannot use it to provide a non-type template argument (as the I in std::get<I>).

Of course one could argue that when called to calculate a constexpr variable the passed arguments are always constant expressions and could be used as such also inside the function. But it would be unexpected if a constexpr function works at compile time but not anymore at runtime.

One could expect that with the new consteval keyword in C++20, one could use normal arguments to consteval functions in constant expressions, since we know that these arguments have to be constant expressions. But this does not seem to be the case: https://godbolt.org/z/guz7FQ Why this is the case I do not know. But in general I like the seperation between normal variables and non-type template arguments.

Must template argument functions be treated as potentially constexpr?

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

Constexpr function evaluation on a template parameter object (MSVC vs clang/gcc)

Yes, seems like a bug to me as well.

The parser seems to have a problem specifically with the member function call via . in the template argument. It works when using ->:

template<B b> A<(&b)->one()> f() { return {}; }

Will consteval functions allow template parameters dependent on function arguments?

No.

Whatever changes the paper will entail, which is little at this point, it cannot change the fact that a non-template function definition is only typed once. Moreover, if your proposed code would be legal, we could presumably find a way to declare a variable of type std::integral_constant<int, i>, which feels very prohibitive in terms of the ODR.

The paper also indicates that parameters are not intended to be treated as core constant expressions in one of its examples;

consteval int sqrsqr(int n) {
return sqr(sqr(n)); // Not a constant-expression at this point,
} // but that's okay.

In short, function parameters will never be constant expressions, due to possible typing discrepancy.



Related Topics



Leave a reply



Submit