Обнаружение constexpr с помощью SFINAE
Я работаю над обновлением кода на С++, чтобы воспользоваться преимуществами новой функциональности на С++ 11. У меня есть класс признаков с несколькими функциями, возвращающими основные типы, которые большую часть времени, но не всегда, возвращают постоянное выражение. Я хотел бы делать разные вещи в зависимости от того, является ли функция constexpr
или нет. Я придумал следующий подход:
template<typename Trait>
struct test
{
template<int Value = Trait::f()>
static std::true_type do_call(int){ return std::true_type(); }
static std::false_type do_call(...){ return std::false_type(); }
static bool call(){ return do_call(0); }
};
struct trait
{
static int f(){ return 15; }
};
struct ctrait
{
static constexpr int f(){ return 20; }
};
int main()
{
std::cout << "regular: " << test<trait>::call() << std::endl;
std::cout << "constexpr: " << test<ctrait>::call() << std::endl;
}
Дополнительный параметр int
/...
существует, так что если обе функции доступны после SFINAE, первый выбирается путем перегрузки разрешения.
Компиляция и запуск этого с помощью Clang 3.2 показывает:
regular: 0
constexpr: 1
Итак, это работает, но я хотел бы знать, является ли код законным С++ 11. Специально, поскольку я понимаю, что правила для SFINAE изменились.
Ответы
Ответ 1
ПРИМЕЧАНИЕ: Я открыл здесь вопрос о том, действительно ли OPs-код действителен. Мой переписанный ниже пример будет работать в любом случае.
но я хотел бы знать, является ли код законным С++ 11
Это, хотя аргумент шаблона по умолчанию можно считать немного необычным. Мне лично нравится следующий стиль лучше, который похож на то, как вы (читаете: I) записываете черту в для проверки существования функции, просто используя шаблон непигового типа параметр и оставить decltype
:
#include <type_traits>
namespace detail{
template<int> struct sfinae_true : std::true_type{};
template<class T>
sfinae_true<(T::f(), 0)> check(int);
template<class>
std::false_type check(...);
} // detail::
template<class T>
struct has_constexpr_f : decltype(detail::check<T>(0)){};
Пример в реальном времени.
Время пояснения ~
Ваш исходный код работает † потому что аргумент шаблона шаблона по умолчанию для экземпляра является точкой создания экземпляра его шаблона функции, то есть в вашем случае в main
, поэтому он не может быть замещенный раньше этого.
§14.6.4.1 [temp.point] p2
Если шаблон функции [...] вызывается таким образом, который использует определение аргумента по умолчанию этого шаблона функции [...], точкой создания аргумента по умолчанию является точка создания экземпляра шаблон функции [...].
После этого это обычные правила SFINAE.
† По крайней мере, я так думаю, это не совсем понятно в стандарте.
Ответ 2
Подсказка @marshall-clow, я собрал несколько более общую версию признака типа для обнаружения constexpr
. Я смоделировал его на std::invoke_result
, но поскольку constexpr
зависит от входов, аргументы шаблона для значений, переданных в, а не типы.
Он несколько ограничен, так как шаблонные аргументы могут быть только ограниченным набором типов, и все они являются константами, когда они добираются до вызов метода. Вы можете легко протестировать метод оболочки constexpr
, если вам нужны другие типы или не константные lvalues для ссылочного параметра.
Итак, несколько больше упражнений и демонстраций, чем действительно полезный код.
И использование template<auto F, auto... Args>
делает его С++ 17-only, нужно gcc 7 или clang 4. MSVC 14.10.25017 не может его скомпилировать.
namespace constexpr_traits {
namespace detail {
// Call the provided method with the provided args.
// This gives us a non-type template parameter for void-returning F.
// This wouldn't be needed if "auto = F(Args...)" was a valid template
// parameter for void-returning F.
template<auto F, auto... Args>
constexpr void* constexpr_caller() {
F(Args...);
return nullptr;
}
// Takes a parameter with elipsis conversion, so will never be selected
// when another viable overload is present
template<auto F, auto... Args>
constexpr bool is_constexpr(...) { return false; }
// Fails substitution if constexpr_caller<F, Args...>() can't be
// called in constexpr context
template<auto F, auto... Args, auto = constexpr_caller<F, Args...>()>
constexpr bool is_constexpr(int) { return true; }
}
template<auto F, auto... Args>
struct invoke_constexpr : std::bool_constant<detail::is_constexpr<F, Args...>(0)> {};
}
Живая демонстрация с прецедентами в wandbox