C++11 constexpr function's argument passed in template argument
Asked Answered
P

4

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)

Plication answered 28/1, 2012 at 13:33 Comment(1)
FWIW, Clang 3.1 HEAD also spews the same errors.Footsie
E
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

Emendate answered 28/1, 2012 at 13:35 Comment(30)
I am still confused by the sentence : an argument to a function cannot be a const expressionUnbroken
@Mr.Anubis: That is why I explained that part in the next para. Did you read it?Emendate
you mean ..func(T t) inside func , t in not constexp , right?Unbroken
That's not true. 10 is actually a constant expression.Alysaalyse
@JohannesSchaub-litb: I know that is a const expression. And I also know that you didn't read the second para in my post.Emendate
lol "And I also know that you didn't read the second para in my post."Unbroken
@Nawaz I don't need to read a correct second part if I want to complain about the wrong first part, do I?Alysaalyse
@JohannesSchaub-litb: I think, sometimes some sentences can be interpreted in many different ways, that is why authors write few more sentences to explain that part which seems confusing (or even wrong). I know that some other author (or even the same author) can re-write the whole thing, making things clear from the first line.Emendate
@Mr.Anubis: No. There is a difference between readonly expression and constant expression. If you write f(const T t), then t is readonly expression.Emendate
I know my code has worked some weeks ago, I just simplify it for the example. A constexpr function is evaluated at compile-time, so I don't know why now (seems to be with a new version of gcc), it can't resolve tfunc<T, t>Plication
@Plication 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.Alysaalyse
Ok, I missed the fact that constexpr functions are just simple functions that can be evaluated at compile-time. But, why g++ force the evaluation as in a run-time context even if g++ knows that func(10) can be evaluated at compile-time (it can also explain why before it works perfectly).Plication
The parameter may be a constant-expression (indirectly). Consider template <typename T> constexpr T func(T t) { return t+t; } Here, the compiler knows how to implement + both at compile-time and at run-time, and hence this code is valid and is suitable for compile-time execution. So, parameters to a constexpr function are a bit schizophrenic - sometimes they are constant-expressions, sometimes they are not.Plenish
@Gravemind, if you intend that the parameter to func will only be used in a compile-time context, then func should take a template int parameter. By using constexpr and simple parameters, you are requesting that the function be compiled/compilable in both contexts, run-time and compile-time. (That's my intuition at the moment anyway.)Plenish
@AaronMcDaid: In other words, (according to you), this should work : ideone.com/xILLF ... but it doesn't.Emendate
@AaronMcDaid : the puprose of my code is to use the type inference of func(10) (here: int) to call the template function tfunc that take a generic type/value as template parameter.Plication
@Nawaz : I've already used decltype in a define, but it's awful and I want to stay in c++11 meta-programming only.Plication
@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.Plenish
So, what do I do ? do I downgrade to g++-4.6 or hope that was a regression and wait for a fix ?Plication
@Gravemind, I don't think you need to downgrade (I'm not sure I understand why you suggested you might downgrade!). Ultimately, not all constexpr are constant-expressions. There are two solutions to the original problem - either upgrade the func parameter to a full constant-expression (i.e Nawaz's solution in this answer), or to downgrade the parameter that is accepted by the tfunc::tfunc constructor from a template-paremeter to a mere constexpr.Plenish
@AaronMcDaid, I want to downgrade because this works on g++-4.6. The only point to do that, is to have a simple syntax for calling a template function (tfunc) with a generic static value template parameter (T t). Yes, I can do tfunc<int, 10>() or tfunc< a_type_long_as_hell, my_simple_value >(), but it's going to be unreadable very quick inside the rest of my code (and I don't want to obfuscate my code with macros). So, I'm still looking for a way to have at the beginning indirect_func(simple_value) (with 1 parameter) and at the end a call of template <typename T, T t> void tfunc().Plication
@Gravemind, do you have any helpful bounds on T and t? For example, if they were guaranteed to be int between 0 and 10, then you could easily write func with a switch statement. In order to make func into a constexpr you would need to replace the switch with something like return t==0 ? tfunc<T,0>() : t==1 ? tfunc<T,1>() ? t==2 ? tfunc<T,2>() : ... and so on.Plenish
@Gravemind, I've added another answer here to directly tackle your goal - although it has other problems.Plenish
you said in previous comments : "There is a difference between readonly expression and constant expression. If you write f(const T t), then t is readonly expression" . Can you please link to some articles which explains about all this in deep? , ThanksUnbroken
@Mr.Anubis: You can read this topic. If still not satisfied, then create a new topic, and quote my statement there, and ask for detail explanation. I'm sure many good answers you will get.Emendate
@AaronMcDaid, In fact, if you want to know what I am doing with all that, this is for a simple implementation of impossibly fast delegates. So, func is used like this bind(&A::method) and (in g++-4.6) it calls _bindMethod<void(A::*)(int), &A::method>() (for example), then I use it to create my fast delegate. But, since g++-4.6.2, according to the c++11 constexpr standart, this doesn't work anymore.Plication
@Gravemind, that blog piece is very interesting. But I must admit I don't see the connection directly. Are you essentially saying that you are unable to change the signatures of func or tfunc, but you still wish them to be able to call each other? i.e. func must have the same signature because it's going to be used in that delegate?Plenish
@Gravemind, .... perhaps you want to be able to take the address of the templated function, much like was done in the blog post you linked to? Like &tfunc<T, t> ?Plenish
@AaronMcDaid, exactly, and use func(&A::f) to simplify and generalize the syntax by using type inference of template functions. But now, I know it uses a bug of g++-4.6: in a static context (func(10) or func(&A::f)), my constexpr func is consider totally static (statements, return, and arguments). func was a meta-programming function but strongly typed (really nice concept by the way). So I haven't completely understood c++11 constexpr. So, now I use a macro as ugly as it is.Plication
@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.Alysaalyse
P
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.

Plenish answered 28/1, 2012 at 14:5 Comment(5)
constexpr int i = something_that_MUST_be_a_constexpr;Footsie
@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.Plenish
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".)Footsie
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 ?Plication
@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 :-) ]Plenish
P
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.

Plenish answered 28/1, 2012 at 20:58 Comment(1)
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.Stigma
F
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.

Franny answered 28/1, 2012 at 13:37 Comment(4)
And constexpr functions can be evaluated at compile time.Footsie
@Footsie 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."Alysaalyse
@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.Footsie
@Footsie it seems you should have put these excellent comments as comments to the question.Alysaalyse

© 2022 - 2024 — McMap. All rights reserved.