Reputation: 191
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)
Upvotes: 17
Views: 10698
Reputation: 27133
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.
Upvotes: 2
Reputation: 27133
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':
constexpr
// Something that may be a constant-expressionYou 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.
Upvotes: 2
Reputation: 361402
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
Upvotes: 10
Reputation: 20616
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.
Upvotes: 1