constexpr overloading

1,162 views
Skip to first unread message

David Stone

unread,
Oct 8, 2013, 10:25:08 PM10/8/13
to std-pr...@isocpp.org
I would like the ability to write different functions depending on whether the argument to a function is a compile-time constant or not. What I want is similar to what is outlined here:

http://stackoverflow.com/questions/15232758/detecting-constexpr-with-sfinae

However, the solution there only works for determining whether a particular function was declared as constexpr. It cannot be used to determine whether a function argument is a known value at compile time. Similarly, the is_constexpr function outlined here: http://stackoverflow.com/questions/13299394/is-is-constexpr-possible-in-c11 cannot be used with enable_if and a trailing return type because function arguments are never considered constexpr, even if they are compile-time constants known to the compiler in a constexpr function. That is to say, the following code will not work:

namespace {

template<typename T>
constexpr typename std::remove_reference<T>::type makeprval(T && t) {
   
return t;
}

#define is_constexpr(e) noexcept(makeprval(e))
 
template<typename T>
constexpr auto f(T && t) -> typename std::enable_if<is_constexpr(t), bool>::type {
   
return true;
}
template<typename T>
constexpr auto f(T && t) -> typename std::enable_if<!is_constexpr(t), bool>::type {
   
return false;
}

}    // namespace
 
int main() {
   
static_assert(f(0), "Should be constexpr");
   
int a = 0;
   
static_assert(!f(a), "Should not be constexpr");
}



Even though the value passed to t in the first case is a compile-time constant expression, it is not usable in a context that requires a constant expression.

I have several use cases for this (and several other people do as well, judging by the number of StackOverflow questions asking if this is possible). The one that is motivating this proposal is a class that represents an integer with compile-time bounds (`ranged_integer`). A ranged_integer in general should only be explicitly constructible from a built-in integer type. However, if it can be statically determined that the value falls within the range of this ranged_integer, then I would like to allow an implicit constructor. Right now, I can only do this if the range of the type of the built-in integer type falls entirely within the range of the ranged_integer. This dramatically reduces the usefulness, as most ranged_integer types will have a range smaller than that of int, but integer literals are by default typed as int.

The practical effect of this is that declaring a constexpr array of ranged_integer is much more cumbersome than it should be. I would like to be able to just declare something like

static constexpr ranged_integer<0, 40> array[] = {
   
5, 7, 2, 8, 3, 0, 36
};



And get compile-time checks that all of the values are in the range. Instead, the best I can do (and require my users to do!) is something like

using type = ranged_integer<0, 40>;
static constexpr type array[] = {
    type
(5), type(7), type(2), type(8), type(3), type(0), type(36)
};



My class also provides automatic flexing of ranges in arithmetic operations. For instance, ranged_integer<0, 1> * ranged_integer<1, 2> gives a type of ranged_integer<0, 2>. When doing arithmetic with constant values, my users have to either accept an unnecessarily wide range (all the type system can tell me is that this number you are multiplying by is equivalent to ranged_integer<INT_MIN, INT_MAX>) or else have them again call the constructor or use a factory function. Currently I have used the factory function approach in my own code, so ranged_integer<0, 2>(1) * make_ranged<5>() gives a type of ranged_integer<0, 10> with a value of 5.

If it were possible to declare a function parameter as constexpr, then we could overload on it and all of these problems would go away. The other solution would be to have a library function is_constexpr that works for function parameters and can be used with enable_if. However, this would be tricky when you account for compiler optimizations that lead to parameters being known compile-time constants in a release build due to inlining, but not in debug builds.
Message has been deleted

MJanes

unread,
Oct 9, 2013, 3:12:23 AM10/9/13
to std-pr...@isocpp.org
as an alternative solution, you could implement a user defined literal, say "_ri", and forbid implicit construction from integers altogether, so as to write:

static constexpr ranged_integer<0, 40> array[] = { 5_ri, 7_ri, // ...

ranged_integer<0, 2>(1) * 5_ri

Bengt Gustafsson

unread,
Oct 10, 2013, 3:40:22 AM10/10/13
to std-pr...@isocpp.org
Wouldn't that mean that the constant 40 is built into _ri? Not very useful if so...

MJanes

unread,
Oct 10, 2013, 4:01:15 AM10/10/13
to std-pr...@isocpp.org
Il giorno giovedì 10 ottobre 2013 09:40:22 UTC+2, Bengt Gustafsson ha scritto:
Wouldn't that mean that the constant 40 is built into _ri? Not very useful if so...

I think not; to my understanding, you can writing something like

template <int N> struct the_literal {};

template <int Min, int Max >
struct the_class
{
   
template<int N> the_class( the_literal<N> i )
   
{
       
static_assert( N>=Min && N<=Max, "oops" );
   
}    
};

the_class
<0,40> arr[] = { the_literal<14>{}, the_literal<31>{} };

where 5_ri would have type the_literal<5> ...

David Krauss

unread,
Oct 11, 2013, 2:55:36 AM10/11/13
to std-pr...@isocpp.org
On 10/10/13 3:40 PM, Bengt Gustafsson wrote:
> Wouldn't that mean that the constant 40 is built into _ri? Not very useful
> if so...

I think you have a chance if you use a literal operator template to
build the values 5, 7, etc into the return type of _ri, then either the
value 40 is received by the conversion function of _ri return or the
ranged_integer provides the converting constructor from a ranged literal.

It might be better to provide a literal template returning some flavor
of std::integral_constant and not tie that part to ranged_integer.

In any case, then you end up shadowing all the runtime operations with
separate meta-evaluation. I *guess* that's what you asked for in the
first place... good luck!

David Stone

unread,
Oct 11, 2013, 11:45:18 AM10/11/13
to std-pr...@isocpp.org
My original design did include a _ranged_integer literal. However, I soon realized that the implementation was far more complex than a make_ranged<n> function template and was actually less flexible. The function template accepts all compile-time constants, not just literal values.

My goal here isn't to do something that cannot be done, but to do something that cannot be done without extra work from users. Right now, if a user of my library tries to make an array, they have to explicitly construct from each literal. I have a program that makes heavy use of arrays of constants (thousands of them), and it is much more succinct and readable without the extra constructor calls, but I get lots of compile-time checks with them. That's a trade-off I would rather not have to make.

A possibly more important example is the naive use of math in my library. If the user tries to add a ranged_integer and a literal (or otherwise constexpr usable value), the bounds of their result have to assume the worst case of an "int" being anywhere in the range [INT_MIN, INT_MAX], even though we know at compile time where it actually is. Without the ability to overload on constexpr, I don't believe I can solve this problem, which makes this type of library much harder to use.

MJanes

unread,
Oct 11, 2013, 1:02:39 PM10/11/13
to std-pr...@isocpp.org
Il giorno venerdì 11 ottobre 2013 17:45:18 UTC+2, David Stone ha scritto:
My original design did include a _ranged_integer literal. However, I soon realized that the implementation was far more complex than a make_ranged<n> function template and was actually less flexible. The function template accepts all compile-time constants, not just literal values.

you can provide both

A possibly more important example is the naive use of math in my library. If the user tries to add a ranged_integer and a literal (or otherwise constexpr usable value), the bounds of their result have to assume the worst case of an "int" being anywhere in the range [INT_MIN, INT_MAX], even though we know at compile time where it actually is.

I don't understand why. Please, consider the following code:

// the class

template< int Min, int Max = Min >
struct ranged_integer { int v; };

template< int N >
struct ranged_integer<N,N> {};

template< int N1, int M1, int N > // or whatever your own "flexing" logic is
ranged_integer
<N1,M1+N> operator+( ranged_integer<N1,M1> r, ranged_integer<N> )
   
{ return {r.v+N}; }

// the literal

template< int e >
struct pow10: std::integral_constant<int, 10 * pow10<e-1>::value > {};

template<>
struct pow10<0>: std::integral_constant<int, 1 > {};

template< int... >
struct make_literal {};

template< int i >
struct make_literal<i>: std::integral_constant<int,i - '0'> {};

template< int first, int... other >
struct make_literal< first, other... >: std::integral_constant<int,
   
( first - '0'  )* pow10<sizeof...(other)>::value + make_literal<other...>::value > {};

template<char... c>
constexpr ranged_integer< make_literal<c...>::value > operator"" _ri () { return {}; }

given the above ( roughly tested ) code, "ranged_integer<1,2>{1} + 223_ri" gives a "ranged_integer<1,225>{224}", this is what you expected, isn't it ?

David Stone

unread,
Oct 11, 2013, 1:19:06 PM10/11/13
to std-pr...@isocpp.org
The literal version provides no value over a non-type function template. It accepts literals but also anything declared constexpr or anything that is a template argument. The only difference is the syntax of `ri<5>()` vs `5_ri`. I can (and already do!) provide math that gives the correct bounds with the sum of two ranged_integer, and all the literal would do is return a ranged_integer (it cannot give me anything extra). My point is that if a user users a regular literal, I cannot provide the same tight bounds that I can if they used my make_ranged function (or a literal version, as I said, they are identical). If I had the ability to overload on constexpr, then I could use its value as a template parameter and provide the type that I am after.

In short, my goal is to burden the user at little as possible and let them mostly do arithmetic as they are used to. If it is harder to use my library than it is to use built-in integers, then many people won't bother.

MJanes

unread,
Oct 12, 2013, 4:11:38 AM10/12/13
to std-pr...@isocpp.org
ok, I got it now, your goal specifically is to let users use built-in integers with your library, provided they are known at compile time. That is, you want an implicit converting constructor from int to ranged_integer<N,M> enabled iff the int is constexpr. Maybe, a compromise solution could be the ability of static_assert'ing that an evaluation is effectively compile-time or not, so having your implicit conversion to give at least an error in the latter case.

Regarding the literal vs make template issue, I disagree on them being identical. I bet that if you ask anybody what a "5_ri" is, most will tell you at least that it's some flavor of "5", even without ever looking at your code. Conversely, the expression "ri<5>()" could mean anything and you'll end up using a much more descriptive name anyway ( as you're doing now, ie make_ranged ) contracdicting your statement that the literal and the template are just syntactical variants, they're not and one should provide both of them ( the literal as a replacement of builtin integers and the template as a way to <explicitly> express the intention of making a rangedinteger ).

MJanes

unread,
Oct 12, 2013, 9:16:41 AM10/12/13
to std-pr...@isocpp.org
BTW, it seems there's already an open proposal on this, take a look at the paper: "N3583 Exploring constexpr at Runtime", http://open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3583.pdf
Reply all
Reply to author
Forward
0 new messages