Gravemind
Gravemind

Reputation: 191

C++11 constexpr function's argument passed in template argument

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

Answers (4)

Aaron McDaid
Aaron McDaid

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

Aaron McDaid
Aaron McDaid

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':

  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.

Upvotes: 2

Sarfaraz Nawaz
Sarfaraz Nawaz

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

Stuart Golodetz
Stuart Golodetz

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

Related Questions