17

This used to work some weeks ago:

template <typename T, T t> T tfunc() { return t + 10; } template <typename T> constexpr T func(T t) { return tfunc<T, t>(); } int main() { std::cout << func(10) << std::endl; return 0; } 

But now g++ -std=c++0x says:

main.cpp: In function ‘constexpr T func(T) [with T = int]’: main.cpp:29:25: instantiated from here main.cpp:24:24: error: no matching function for call to ‘tfunc()’ main.cpp:24:24: note: candidate is: main.cpp:16:14: note: template<class T, T t> T tfunc() main.cpp:25:1: warning: control reaches end of non-void function [-Wreturn-type] 

clang++ -std=c++11 says that template's parameters of tfunc<T, t>() are ignored because invalid.

Is that a bug, or a fix ?

PS:

g++ --version => g++ (GCC) 4.6.2 20120120 (prerelease)

clang++ --version => clang version 3.0 (tags/RELEASE_30/final) (3.0.1)

1
  • FWIW, Clang 3.1 HEAD also spews the same errors. Commented Jan 28, 2012 at 13:44

4 Answers 4

10

The parameter t is not a constant expression. Hence the error. It should be also noted that it cannot be a constant expression.

You can pass the constant expression as argument, but inside the function, the object (the parameter) which holds the value, is not a constant expression.

Since t is not a constant expression, it cannot be used as template argument:

return tfunc<T, t>(); //the second argument must be a constant expression 

Maybe, you want something like this:

template <typename T, T t> T tfunc() { return t + 10; } template <typename T, T t> //<---- t became template argument! constexpr T func() { return tfunc<T, t>(); } #define FUNC(a) func<decltype(a),a>() int main() { std::cout << FUNC(10) << std::endl; } 

Now it should work : online demo

Sign up to request clarification or add additional context in comments.

30 Comments

lol "And I also know that you didn't read the second para in my post."
@Nawaz I don't need to read a correct second part if I want to complain about the wrong first part, do I?
@Gravemind because a constexpr function is a function. The function behind this still is a normal function. That this fails has nothing to do with constexpr, but just with the normal rule that function parameters cannot be used as template arguments.
@Nawaz, that example works on g++-4.6. And I think that's the correct behaviour, and that ideone is out-of-date. But I might be wrong.
@Aaron parameters of constexpr functions are never constant expressions. Any time you call a constexpr function within a constant expression, function invocation substitution is done. Any parameter names used in the return statement are then replaced by the argument expression. So no parameters are used, but the arguments are used directly.
|
2

I get the feeling that constexpr must also be valid in a 'runtime' context, not just at compile-time. Marking a function as constexpr encourages the compiler to try to evaluate it at compile-time, but the function must still have a valid run-time implementation.

In practice, this means that the compiler doesn't know how to implement this function at runtime:

template <typename T> constexpr T func(T t) { return tfunc<T, t>(); } 

A workaround is to change the constructor such that it takes its t parameter as a normal parameter, not as a template parameter, and mark the constructor as constexpr:

template <typename T> constexpr T tfunc(T t) { return t + 10; } template <typename T> constexpr T func(T t) { return tfunc<T>(t); } 

There are three levels of 'constant-expression-ness':

  1. template int parameter, or (non-VLA) array size // Something that must be a constant-expression
  2. constexpr // Something that may be a constant-expression
  3. non-constant-expression

You can't really convert items that are low in that list into something that is high in that list, but obviously the other route it possible.

For example, a call to this function

constexpr int foo(int x) { return x+1; } 

isn't necessarily a constant-expression.

// g++-4.6 used in these few lines. ideone doesn't like this code. I don't know why int array[foo(3)]; // this is OK int c = getchar(); int array[foo(c)]; // this will not compile (without VLAs) 

So the return value from a constexpr function is a constant expression only if all the parameters, and the implementation of the function, can be completed at executed at compile-time.

5 Comments

constexpr int i = something_that_MUST_be_a_constexpr;
@Xeo, it must at least be a constexpr. Whereas template<int t> struct X; X<i> x; requires that i be even more than a constexpr.
Maybe I shouldn't have used the second constexpr as a short-hand for constant expression... :) I just wanted to say that for constexpr, sometimes something must be a constant expression - when a variable is declared as such. (As an answer to your "constexpr // Something that may be a constant-expression".)
This example shows exactly why I don't understand why my code doesn't work: foo(3) and foo(c) are 2 totally different interpretations by g++. foo(3) will be evaluated at compile-time, and foo(c) will generate the code to do it at run-time. So, why foo(3) is evaluated as a run-time function ?
@Xeo, I agree that "may" is a bad choice of word. I'll think further about how to edit that, and you can feel free to edit it yourself. There is a certain 'split-personality' around constexpr. A constexpr only graduates to be a full 'constant-expression' if certain conditions hold. I'm sure you understand that already, the challenge is to summarize this for the layman. [ And I'm no expert, hence I'm furiously testing all this on various compilers - where each compiler is behaving a little differently :-) ]
2

Recap the question: You have two functions which take a parameter of type T. One takes its parameter as a template parameter, and the other as a 'normal' parameter. I'm going to call the two functions funcT and funcN instead of tfunc and func. You wish to be able to call funcT from funcN. Marking the latter as a constexpr doesn't help.

Any function marked as constexpr must be compilable as if the constexpr wasn't there. constexpr functions are a little schizophrenic. They only graduate to full constant-expressions in certain circumstances.

It would not be possible to implement funcN to run at runtime in a simple way, as it would need to be able to work for all possible values of t. This would require the compiler to instantiate many instances of tfunc, one for each value of t. But you can work around this if you're willing to live with a small subset of T. There is a template-recursion limit of 1024 in g++, so you can easily handle 1024 values of T with this code:

#include<iostream> #include<functional> #include<array> using namespace std; template <typename T, T t> constexpr T funcT() { return t + 10; } template<typename T, T u> constexpr T worker (T t) { return t==0 ? funcT<T,u>() : worker<T, u+1>(t-1); } template<> constexpr int worker<int,1000> (int ) { return -1; } template <typename T> constexpr T funcN(T t) { return t<1000 ? worker<T,0>(t) : -1; } int main() { std::cout << funcN(10) << std::endl; array<int, funcN(10)> a; // to verify that funcN(10) returns a constant-expression return 0; } 

It uses a function worker which will recursively convert the 'normal' parameter t into a template parameter u, which it then uses to instantiate and execute tfunc<T,u>.

The crucial line is return funcT<T,u>() : worker<T, u+1>(t-1);

This has limitations. If you want to use long, or other integral types, you'll have to add another specialization. Obviously, this code only works for t between 0 and 1000 - the exact upper limit is probably compiler-dependent. Another option might be to use a binary search of sorts, with a different worker function for each power of 2:

template<typename T, T u> constexpr T worker4096 (T t) { return t>=4096 ? worker2048<T, u+4096>(t-4096) : worker2048<T, u>(t); } 

I think this will work around the template-recursion-limit, but it will still require a very large number of instantiations and would make compilation very slow, if it works at all.

1 Comment

you can verify a constexpr function by declaring an constexpr auto or the specific return type in the main(since c++17). Basically constexpr auto var = funcN(10). If it works, it's constexpr.
1

Looks like it should give an error - it has no way of knowing that you passed in a constant value as t to func.

More generally, you can't use runtime values as template arguments. Templates are inherently a compile-time construct.

4 Comments

And constexpr functions can be evaluated at compile time.
@Xeo I don't see the relevance of your comment to this answer. It sounds like, I say "My hand cannot be used to walk around." and you say "And your arm can be used to move your hand."
@Johannes: Well, since constexpr functions can be evaluated at compile time, their arguments may to be known at compile-time, and as such they could be used as template arguments - if we could specify that we only allow compile-time values as arguments. Sadly, constexpr void f(constexpr int x); doesn't work. I know that you could just say template<int x> constexpr void f(); at that point, but still... Template arguments are not subject to conversions, for example.
@Xeo it seems you should have put these excellent comments as comments to the question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.