Hello,
The Problem
The Standard Library provides a great collection of algorithms, many of which currently lack constexpr support. Consider the simple example:
#include <array>
#include <algorithm>
int main() {
// OK
constexpr std::array<char, 6> a { 'H', 'e', 'l', 'l', 'o' };
// Note: std::array::begin(), std::array::end() are constexpr in P0031R0
//
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0031r0.html // Failures:
// * std::find is not constexpr
constexpr auto it = std::find(a.begin(), a.end(), 'H');
}
Main problem with functions from <algorithm> is that they could be optimized using assembly. Currently constexpr can not deal with inline assembly.
Let's discuss and share ideas on how to improve the situation.
Ideas
Here are the ideas I've been investigating. Impatient ones could jump right to the 'Refinement' to see the idea I've stopped on.
-----------------------------------------------
IDEA #1
Create a trait that signals usage of function inside constexpr context: `__is_constexpr_context` that returns true_type or false_type
EXAMPLE in pseudocode:
template <class It, class Value>
constexpr It find_impl(It begin, It end, const Value& v, true_type /*constexpr*/);
template <class It, class Value>
It find_impl(It begin, It end, const Value& v, false_type /*constexpr*/);
template <class It, class Value>
constexpr It find(It begin, It end, const Value& v) {
return find_impl(begin, end, v, __is_constexpr_context{});
}
DRAWBACKS:
* Breaks ODR (This is probably one of the worst ideas ever)
* Potentially forces user to write two different implementations of function
-----------------------------------------------
IDEA #2
Allow overloads by constexpr.
EXAMPLE in pseudocode:
template <class It, class Value>
It find(It begin, It end, const Value& v);
template <class It, class Value>
constexpr It find(It begin, It end, const Value& v);
DRAWBACKS:
* forces user to write two different implementations of function
* C++ already has a lot of overload rules, adding new ones does not seem to be a good idea. For example which overload to choose is not obvious in following case:
template <class T> constexpr void foo(const T& );
void foo(int );
-----------------------------------------------
IDEA #3
Relax restrictions on constexpr, allow usage of assembly and goto in constexpr functions.
DRAWBACKS:
* Providing such ability for cross-compilaton would be a huge amount of work for the compiler developers. Compiler developers would be forced to know how to evaluate assembly for a foreign platform. For example cross compiling on x86 for ARM will require the compiler to evaluate ARM assembly on x86.
-----------------------------------------------
IDEA #4
Mark the required function with constexpr and force the Standard Library developers and compiler developers to implement more compiler intrinsics.
EXAMPLE in pseudocode:
template <class It, class Value>
constexpr It find(It begin, It end, const Value& v) {
return __builtin_find(begin, end, v);
}
DRAWBACKS:
* This will make the Standard Libraries less portable across compilers (every compiler vendor has it's own naming schemes for intrinsics).
* This solution is also not usable by user (user would not be able to create it's own assembly optimized constexpr function, because he has no access to the compiler internals).
-----------------------------------------------
IDEA #5
Add an additional keyword, signaling that in a constexpr context only this overload must be used.
EXAMPLE in pseudocode:
template <class T>
constexpr_only // <---- new keyword
int foo(const T& ); // overlaod #1
int foo(int ); // overlaod #2
constexpr int foo(float ); // overlaod #3
int main() {
constexpr int i0 = foo(1); // will call overlaod #1
constexpr int i1 = foo(1.0); // will call overlaod #1
constexpr int i2 = foo(1.0f); // will call overlaod #3
int i3 = foo(1LL); // will call overlaod #2
int i4 = foo(1.0f); // will call overlaod #3
}
DRAWBACKS:
* New keyword is not good.
* This idea also affects the overload rules and make them even more confusing.
* Potentially forces user to write two different implementations of function
-----------------------------------------------
Refinement
I've stopped on idea #3. In attempt to simplify the compiler developers work, I've applied additional restrictions to inline assembly.
I took additional care to not mention the asm in [expr.const], because asm definition is implementation-defined (see [dcl.asm]).
The impact on the C++ Standard is following:
In [dcl.constexpr] remove the following requirements on constexpr function body:
— an asm-definition,
— a goto statement,
In [expr.const] add the following to the list of the "would evaluate one of the following expressions:"
— a call to priviledged instruction, long jump instruction, software interruption or any other instruction that
produces results depending not only on input arguments [ Example: x86 instructions CPUID, RDRAND, RDSEED — end example ].
-----------------------------------------------
Any suggestions or improvements are welcomed!
--
Best regards,
Antony Polukhin