constexpr only functions: arbitrary literal types for non-type, constexpr arguments, is_constexpr...

169 views
Skip to first unread message

Bastien Penavayre

unread,
Aug 31, 2017, 2:43:27 PM8/31/17
to ISO C++ Standard - Future Proposals
Hi, 

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,

Ricardo Fabiano de Andrade

unread,
Aug 31, 2017, 4:17:41 PM8/31/17
to std-pr...@isocpp.org
Very interesting proposal. Looking forward to hearing additional feedback from others, I hope it moves forward.
Simple syntax question: why not placing constexpr() next to where noexcept() is, at the end of the function declaration/definition?
This alternative seems more consistent, even with lamba functions examples.

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/17e156a0-0368-4ec0-a087-e8b4bc8d9f62%40isocpp.org.

Bastien Penavayre

unread,
Aug 31, 2017, 5:32:27 PM8/31/17
to ISO C++ Standard - Future Proposals
Le jeudi 31 août 2017 22:17:41 UTC+2, Ricardo Andrade a écrit :
Very interesting proposal. Looking forward to hearing additional feedback from others, I hope it moves forward.
Thanks.
Simple syntax question: why not placing constexpr() next to where noexcept() is, at the end of the function declaration/definition?
That's a good question. I'm not opposed to the idea but my motivation was the following:

To make as little change as possible to the existing grammar.
As of now constexpr is a front specifier for function/members, tail specifier for lambdas and syntactically I'm only expending it, i'm not creating something new.
auto func() constexpr; //invalid now

 | This alternative seems more consistent, even with lambda functions examples.
You can notice that it's the case with the lambdas in my exemple and I agree,
but for now lambdas and functions/methods don't have the same syntax to express their constexpr-ness.
A Proposal to correct this wouldn't be affected by this proposal.

On a more design oriented front, as constexpr would allow "specialization" I feel like it's not a bad idea to put it at the front of the function, to make it more visible.

I hope I've answered your question correctly.
If you have any other, please, feel free to ask.

Ricardo Fabiano de Andrade

unread,
Aug 31, 2017, 6:22:52 PM8/31/17
to std-pr...@isocpp.org
I was interested in the motivation behind your choice and after the explanation I am more open to it.
I'll be following the thread.

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Bastien Penavayre

unread,
Sep 15, 2017, 5:31:05 AM9/15/17
to ISO C++ Standard - Future Proposals
Hi,  
I've formulated this idea a while ago now but I haven't gathered much feedback.
My idea is to allow constexpr specialization without impeding on existant constexpr rules by allowing constexpr to take a bool literal a la noexcept.
The reason as to why is that It would allow to answer the demand for arbitrary literal types for non-type template parameter, constexpr arguments in function, is_constexpr, gnu string literal operator, operator[] for std::tuple and would lead in general to cleaner code.
And all of that without new keyword, mangling problems, etc...

There are a bunch of proposals floating around, that, I think, are interesting but have issues implementation-wise.
//constexpr is ignored, retains inline side effect, may warn

constexpr auto func2();

constexpr auto func3();
constexpr auto error_0 = func3();
//error: 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 [/*no capture required as constant is constexpr*/](auto&&... runtime_args) constexpr /*normal constexpr lambda*/
   {
       
//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...>&&)
}
Reply all
Reply to author
Forward
0 new messages