问题
Related: Function returning constexpr does not compile
I feel like constexpr is limited in usefulness in C++11 because of the inability to define two functions that would otherwise have the same signature, but have one be constexpr and the other not constexpr. In other words, it would be very helpful if I could have, for example, a constexpr std::string constructor that takes constexpr arguments only, and a non-constexpr std::string constructor for non-constexpr arguments. Another example would be a theoretically complicated function that could be made more efficient by using state. You can't easily do that with a constexpr function, so you are left with two choices: have a constexpr function that is very slow if you pass in non-constexpr arguments, or give up on constexpr entirely (or write two separate functions, but you may not know which version to call).
My question, therefore, is this:
Is it possible for a standard-compliant C++11 implementation to allow function overloading based on the arguments being constexpr, or would this require updating the standard? If it is not allowed, was it intentionally not allowed?
@NicolBolas: Say I have a function that maps an enum
to a std::string
. The most straight-forward way to do this, assuming my enum
goes from 0
to n - 1
, is to create an array of size n
filled with the result.
I could create a static constexpr char const * []
and construct a std::string
on return (paying the cost of creating a std::string
object every time I call the function), or I can create a static std::string const []
and return the value I look up, paying the cost of all of the std::string
constructors the first time I call the function. It seems like a better solution would be to create the std::string
in memory at compile time (similar to what is done now with char const *
), but the only way to do this would be to alert the constructor that it has constexpr
arguments.
For a an example other than a std::string
constructor, I think it's pretty straight-forward to find an example where, if you could ignore the requirements of constexpr
(and thus create a non-constexpr
function), you could create a more efficient function. Consider this thread: constexpr question, why do these two different programs run in such a different amount of time with g++?
If I call fib
with a constexpr
argument, I can't beat do better than the compiler optimizing away the function call entirely. But if I call fib
with a non-constexpr
argument, I may want to have it call my own version that implements things like memoization (which would require state) so I get run time similar to what would have been my compile time had I passed a constexpr
argument.
回答1:
It would have to be overloaded based on the result being constexpr
or not, rather than the arguments.
A const std::string
could store a pointer to the literal, knowing that it would never be written to (using const_cast
to remove const
from the std::string
would be necessary, and that's already undefined behavior). It'd just be necessary to store a boolean flag to inhibit freeing the buffer during destruction.
But a non-const
string, even if initialized from constexpr
arguments, requires dynamic allocation, because a writable copy of the argument is required, and therefore a hypothetical constexpr
constructor should not be used.
From the standard (section 7.1.6.1 [dcl.type.cv]
), modifying any object which was created const
is undefined behavior:
Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.
回答2:
I agree that this feature is missing - I need it too. Example:
double pow(double x, int n) {
// calculate x to the power of n
return ...
}
static inline double pow (double x, constexpr int n) {
// a faster implementation is possible when n is a compile time constant
return ...
}
double myfunction (double a, int b) {
double x, y;
x = pow(a, b); // call version 1 unless b becomes a compile time constant by inlining
y = pow(a, 5), // call version 2
return x + y;
}
Now I have to do this with templates:
template <int n>
static inline double pow (double x) {
// fast implementation of x ^ n, with n a compile time constant
return ...
}
This is fine, but I miss the overload opportunity. If I make a library function for others to use then it is inconvenient that the user has to use different function calls depending on whether n is a compile time constant or not, and it may be difficult to predict whether the compiler has reduced n to a compile time constant or not.
回答3:
Detecting constexpr
can't be made using overloads (like others already replied) but overloads are just one way to do it.
The typical problem is that we can't use something that can improve run-time performance (for example to call non-constexpr
functions or to cache results) in constexpr
function. So we may end up with two different algorithms, one less efficient but writable as constexpr
, other optimized to run fast but not constexpr
. Then we want compiler not to choose the constexpr
algorithm for run-time values and vice versa.
That can be achieved by detecting constexpr
and selecting based on it "manually" and then shortening the interface down with preprocessor macros.
First lets have two functions. In general the functions should reach same result with different algorithms. I choose two algorithms that never give same answers here just to test and to illustrate the idea:
#include <iostream> // handy for test I/O
#include <type_traits> // handy for dealing with types
// run-time "foo" is always ultimate answer
int foo_runtime(int)
{
return 42;
}
// compile-time "foo" is factorial
constexpr int foo_compiletime(int num)
{
return num > 1 ? foo_compiletime(num - 1) * num : 1;
}
Then we need a way to detect that argument is compile time constant expression. If we don't want to use compiler-specific ways like __builtin_constant_p
then there are ways to detect it in standard C++ as well. I'm pretty sure that following trick is invented by Johannes Schaub but I can't find the cite. Very nice and clear trick.
template<typename T>
constexpr typename std::remove_reference<T>::type makeprval(T && t)
{
return t;
}
#define isprvalconstexpr(e) noexcept(makeprval(e))
The noexcept
operator is required to work compile-time and so branching based on it will be optimized out by most compilers. So now we can write a "foo" macro that selects the algorithm based on argument's constexprness and to test it:
#define foo(X) (isprvalconstexpr(X)?foo_compiletime(X):foo_runtime(X))
int main(int argc, char *argv[])
{
int a = 1;
const int b = 2;
constexpr int c = 3;
const int d = argc;
std::cout << foo(a) << std::endl;
std::cout << foo(b) << std::endl;
std::cout << foo(c) << std::endl;
std::cout << foo(d) << std::endl;
}
Expected output is:
42
2
6
42
On the few compilers that I tried it works like expected.
回答4:
While there is no such thing as "constexpr overloading" in C++11, you can still use GCC/Clang __builtin_constant_p
intrinsic. Note, that this optimization is not very useful for double pow(double)
, because both GCC and Clang already can optimize pow for constant integral exponents, but if you write a multiprecision or vector library, then this optimization should work.
Check this example:
#define my_pow(a, b) (__builtin_constant_p(b) ? optimized_pow(a, b) : generic_pow(a, b))
double generic_pow(double a, double b);
__attribute__((always_inline)) inline double optimized_pow(double a, double b) {
if (b == 0.0) return 1.0;
if (b == 1.0) return a;
if (b == 2.0) return a * a;
if (b == 3.0) return a * a * a;
if (b == 4.0) return a * a * a * a;
return generic_pow(a, b);
}
double test(double a, double b) {
double x = 2.0 + 2.0;
return my_pow(a, x) + my_pow(a, b);
}
In this example my_pow(a, x)
will be expanded to a*a*a*a
(thanks to dead code elimination), and my_pow(a, b)
will be expanded to direct generic_pow
call without any preliminary checks.
回答5:
The problem, as stated, feels wrong.
A std::string
, by construction, owns the memory. If you want a simple reference to an existing buffer, you can use something akin to llvm::StringRef
:
class StringRef {
public:
constexpr StringRef(char const* d, size_t s): data(d), size(s) {}
private:
char const* data;
size_t size;
};
Of course, there is the bummer that strlen
and all the others C functions are not constexpr
. This feels like a defect of the Standard (think about all the maths functions...).
As for state, you can (a bit), as long as you understand how to store it. Remember that loops are equivalent to recursions ? Well, likewise, you can "store" state by passing it as argument to a helper function.
// potentially unsafe (non-limited)
constexpr int length(char const* c) {
return *c == '\0' ? 0 : 1 + length(c+1);
}
// OR a safer version
constexpr int length_helper(char const* c, unsigned limit) {
return *c == '\0' or limit <= 0 ? 0 : 1 + length_helper(c+1, limit-1);
}
constexpr int length256(char const* c) { return length_helper(c, 256); }
Of course, this form of this state is somewhat limited (you cannot use complicated constructs) and that is a limitation of constexpr
. But it's already a huge leap forward. Going further would mean going deeper into purity (which is hardly possible in C++).
回答6:
Is it possible for a standard-compliant C++11 implementation to allow function overloading based on the arguments being constexpr, or would this require updating the standard? If it is not allowed, was it intentionally not allowed?
If the standard doesn't say you can do something, then allowing someone to do it would be non-standard behavior. And therefore, a compiler that allowed it would be implementing a language extension.
That's not necessarily a bad thing, after all. But it wouldn't be compliant C++11.
We can only guess at the intentions of the standards committee. They may have deliberately not allowed it, or it may have been something of an oversight. The fact is that the standard doesn't overloading is allowed, therefore it isn't.
回答7:
Another option to detect compile-time compilation using SFINAE: http://coliru.stacked-crooked.com/a/f3a2c11bcccdb5bf
template<typename T>
auto f(const T&)
{
return 1;
}
constexpr auto f(int)
{
return 2;
}
////////////////////////////////////////////////////////////////////////
template<typename T, int=f(T{})>
constexpr bool is_f_constexpr_for(int) {return true;}
template<typename...>
constexpr bool is_f_constexpr_for(...) {return false;}
template<typename T>
auto g(const T& t)
{
if constexpr (is_f_constexpr_for<T>(0))
{
}
else
{
}
}
来源:https://stackoverflow.com/questions/8936549/constexpr-overloading