Ответ 1
Можно определить, является ли данное выражение функции-выражения постоянным выражением и тем самым выбирать между двумя различными реализациями. Требуется С++ 14 для общей лямбда, используемой ниже.
(Этот ответ получил этот ответ от @Yakk на вопрос, который я задал в прошлом году).
Я не уверен, насколько я продвигаю стандарт. Это проверено на clang 3.9, но приводит к тому, что g++ 6.2 дает "внутреннюю ошибку компилятора". Я отправлю отчет об ошибке на следующей неделе (если никто не сделает это первым!)
Этот первый шаг - переместить реализацию constexpr
в struct
как метод constexpr static
. Более просто, вы можете оставить текущий constexpr
как есть и вызвать его из метода constexpr static
нового struct
.
struct StaticStruct {
static constexpr float MyMin_constexpr (float a, float b) {
return a<b?a:b;
}
};
Также определите это (хотя оно выглядит бесполезно!):
template<int>
using Void = void;
Основная идея состоит в том, что Void<i>
требует, чтобы i
являлось константным выражением. Точнее, эта следующая лямбда будет иметь соответствующие перегрузки только в определенных обстоятельствах:
auto l = [](auto ty)-> Void<(decltype(ty):: MyMin_constexpr(1,3) ,0)>{};
\------------------/
testing if this
expression is a
constant expression.
Мы можем вызывать l
, только если аргумент ty
имеет тип StaticStruct
и если наше представляющее интерес выражение (MyMin_constexpr(1,3)
) является постоянным выражением. Если мы заменим 1
или 3
на непостоянные аргументы, то общий лямбда l
потеряет метод через SFINAE.
Следовательно, следующие два теста эквивалентны:
- Является ли
StaticStruct::MyMin_constexpr(1,3)
постоянным выражением?- Может
l
вызываться черезl(StaticStruct{})
?
Заманчиво просто удалить auto ty
и decltype(ty)
из приведенной выше лямбда. Но это приведет к жесткой ошибке (в непостоянном случае) вместо приятного смены замены. Поэтому мы используем auto ty
, чтобы получить сбой замены (который мы можем с пользой обнаруживать) вместо ошибки.
Этот следующий код - это простая вещь, чтобы вернуть std:true_type
тогда и только тогда, когда f
(наш общий лямбда) можно вызвать с помощью a
(StaticStruct
):
template<typename F,typename A>
constexpr
auto
is_a_constant_expression(F&& f, A&& a)
-> decltype( ( std::forward<F>(f)(std::forward<A>(a)) , std::true_type{} ) )
{ return {}; }
constexpr
std::false_type is_a_constant_expression(...)
{ return {}; }
Далее, демонстрация этого использования:
int main() {
{
auto should_be_true = is_a_constant_expression(
[](auto ty)-> Void<(decltype(ty):: MyMin_constexpr(1,3) ,0)>{}
, StaticStruct{});
static_assert( should_be_true ,"");
}
{
float f = 3; // non-constexpr
auto should_be_false = is_a_constant_expression(
[](auto ty)-> Void<(decltype(ty):: MyMin_constexpr(1,f) ,0)>{}
, StaticStruct{});
static_assert(!should_be_false ,"");
}
}
Чтобы решить вашу исходную проблему напрямую, мы могли бы сначала определить макрос, чтобы сохранить повторение:
(Я не тестировал этот макрос, извиняюсь за любые опечатки.)
#define IS_A_CONSTANT_EXPRESSION( EXPR ) \
is_a_constant_expression( \
[](auto ty)-> Void<(decltype(ty):: \
EXPR ,0)>{} \
, StaticStruct{})
На этом этапе, возможно, вы могли бы просто сделать:
#define MY_MIN(...) \
IS_A_CONSTANT_EXPRESSION( MyMin_constexpr(__VA_ARGS__) ) ? \
Static_Struct :: MyMin_constexpr( __VA_ARGS__ ) : \
MyMin_runtime ( __VA_ARGS__ )
или, если вы не доверяете компилятору оптимизировать std::true_type
и std::false_type
через ?:
, то возможно:
constexpr
float MyMin(std::true_type, float a, float b) { // called if it is a constant expression
return StaticStruct:: MyMin_constexpr(a,b);
}
float MyMin(std::false_type, float , float ) { // called if NOT a constant expression
return MyMin_runtime(a,b);
}
вместо этого макроса:
#define MY_MIN(...) \
MyMin( IS_A_CONSTANT_EXPRESSION(MyMin_constexpr(__VA_ARGS__)) \
, __VA_ARGS__)