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 typeT
is a literal constant expression,
implicitly converted to the typeT
, [...]
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
How to Use Non-Default Delimiters When Reading a Text File with Std::Fstream
Decimal to Hex Conversion C++ Built-In Function
On How to Recognize Rvalue or Lvalue Reference and If-It-Has-A-Name Rule
Profiler for Visual Studio 2008, C++
Special Simple Random Number Generator
Serializing Opencv Mat_<Vec3F>
Address-Of Operator (&) VS Reference Operator(&)
Passing a Pointer Representing a 2D Array to a Function in C++
Is the Behavior of Subtracting Two Null Pointers Defined
Pass by Reference and Value with Pointers
Call Destructor and Then Constructor (Resetting an Object)
Disable Eclipse's Error Discovery. (Codan False Positives)
Stopping the Debugger When a Nan Floating Point Number Is Produced
Why Doesn't Reinterpret_Cast Force Copy_N for Casts Between Same-Sized Types