Подтвердить что ты не робот

Обнаружение 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 изменились.

4b9b3361

Ответ 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