So let me pressent the current state of affairs.
There are a bunch of proposals floating around, that, I think, are interesting but have issues implementation-wise or are a bit clunky.
N3413 (arbitrary literal types for non-type) for instance requires that a bunch of controversial questions to be answered to be accepted (mangling, operator== or bitwise, ...).
Because of that this proposal will not come around any time soon.
At the same time demands to check or enforce constexpr-ness in functions were made through the ideas of forced constexpr arguments and is_constexpr().
- The first has the same issues as N3413 as it requires said constexpr arguments be integrated into the mangling of the function, making this proposal syntax sugar to N3413.
//this
template<Literal l> void func();
//differs only through call syntax from this
void func(constexpr Literal l);
//yet this would allow to get rid of atrocious code like this
template<auto I>
constexpr auto func(std:integral_constant<decltype(I), I>); //only uses I
- The latter (is_constexpr) aims to allow a runtime or compile-time specialization through a constexpr expression with a variant value. It would requires long if constexpr blocks as, if runtime specialization is required, we can assume that the runtime and compile-time version will share very little code. It would also mean that, despite the confirmation by the compiler that we are in constexpr context with arguments being compile-time evaluated, we would still be incapable to use those as such or would require that if constexp(is_constexpr()) have side-effects (big no-no).
constexpr auto func(int i)
{
if constexpr (is_constexpr())
{
//i is still not usable as a constexpr
//long piece of code or call to other function that handles the compile-time version
...
}
else
{
//long piece of code or call to other function that handles the runtime version
....
}
}
Now that the context has been presented here is my proposal with a detailed and commented exemple.
My goal is to allow a big chunk of what the other proposal aimed to allow without all their issues and in one proposal.
It is to allow constexpr-only specialization by borrowing the noexcept syntax.
As the function may only be constexpr it will not have linkage and therefor no mangling nor all the issues associated with it.
Now here is a detailed exemple.
//compile-time only function
//it will never have linkage
//all its arguments are constexpr
//a non-constexpr expression usage in its definition will cause an error instead of a fallback like normal constexpr
//is only called in an explicit constexpr context unlike normal constexpr
constexpr(true) auto func(Literal r);
//runtime specialization of func
/*constexpr(false)*/ auto func(Literal r); //constexpr(false) is the default for functions and methods
//error redefinition of 'constexpr(false) void func(Literal)'
constexpr void func();
constexpr(true) auto func2();
//allowed
//constexpr is ignored, retains inline side effect
constexpr auto func2();
constexpr auto func3();
constexpr auto error_0 = func3();
//constexpr specialization of 'func3' after instanciation
constexpr(true) auto func3();
//addresses demands for partially constexpr argument-list
constexpr(true) void partial_constexpr(int constant)
{
return [&](auto&&... runtime_args) constexpr /*this lambda has the default constexpr behavior*/ {
//runtime code
//can use 'constant' as a constexpr here
};
}
int main()
{
constexpr auto r_0 = func(Literal{}); //constexpr(true) version called
auto r_1 = func(Literal{}); //constexpr(false) version called
partial_constexpr(42/*compile-time args*/)(/*runtime args*/);
auto r_2 = constexpr func(Literal{}); //also proposed unary constexpr operator for explicit selection, error if the expression is not constexpr
//is equivalent to
auto r_3 = []() { constexpr auto __ret__ = func(Literal{}); return __ret__; }();
}
//replacing of gnu's operator"" extension
constexpr(true) auto operator""_string_literal(const char *str, size_t len)
{
return [&]<size_t... Indexs>(std::index_sequence<Indexs...>&&)
{
return TemplateCharSeq<str[Indexs]...>{};
}(std::make_index_sequence<len>{});
}
//operator[] for std::tuple
template <class... Args>
struct tuple
{
...
constexpr(true) auto& operator[](size_t i) const
{
static_assert(i < sizeof...(Args));
return std::get<i>(*this);
}
constexpr(true) auto& operator[](size_t i)
{
static_assert(i < sizeof...(Args));
return std::get<i>(*this);
}
....
};
template <class... Args> struct Select : Args... { using Args::operator()...; };
template <class... Args> Select(Args&&...) -> Select<Args...>;
//for lambdas
void func_3()
{
constexpr auto f_0 = []() {}; //operator() is constexpr /*fallback on runtime*/, no changes
constexpr auto f_1 = []() constexpr(false) {}; //operator() cannot be called at compile-time
constexpr auto f_2 = []() constexpr(true) {}; //operator() can only be called a compile-time and explicitly
constexpr Select f_3 = { f_1, f_2 };
constexpr auto r_4 = f_3(); //f_2
auto r_5 = f_3(); //f_1
}
Here is the summary:
- extend constexpr specifier to allow it to take a constexpr explicit bool (no a constexpr expression unless if there are good use-cases for it) where:
- constexpr(false) is the default specifier of a function/method
- optional for functions/method.
- if specified with lambdas allows not-constexpr lambda as lambdas are by default constexpr.
- constexpr(true) is a constexpr only function.
- no Assembly representation (no linkage nor mangling).
- all arguments are constexpr.
- error if non-constexpr expression is used (a constexpr fallback to runtime because of an overflow for instance).
- only used in explicit constexpr context.
- cannot be called before its specialization if specialization or else error.
- constexpr
- keeps all its properties and definition.
- will still trigger redefinition error if previous constexpr(false) version previously defined.
- constexpr attribute ignored if previous constexpr(true) is defined.
- unary constexpr operator to request a constexpr expression explicitly:
auto t = constexpr expr; //might error if expr is no a constexpr expression
The reason why I believe in this proposal:
- no new keyword.
- zero runtime impact.
- maintains backward compatibility.
- solves, at least partially, a lot of different proposals.
Thanks in advance,
Bastien Penavayre,