GOOGLE ADS

понедельник, 9 мая 2022 г.

SFINAE не работает в рекурсивной функции

Давайте создадим функцию каррирования.

template <typename TFunc, typename TArg>
class CurryT
{
public:
CurryT(const TFunc &func, const TArg &arg)
: func(func), arg(arg )
{}
template <typename... TArgs>
decltype(auto) operator()(TArgs...args) const
{ return func(arg, args...); }
private:
TFunc func;
TArg arg;
};
template <typename TFunc, typename TArg>
CurryT<decay_t<TFunc>, remove_cv_t<TArg>>
Curry(const TFunc &func, const TArg &arg)
{ return {func, arg}; }

И функция, которая отделяет функцию от функции с одним аргументом:

// If single argument function (F(int)).
template <typename F>
static auto Decouple(const F &f, enable_if_t<is_invocable_v<F, int>> * = nullptr)
{
return f;
}
// If multiple arguments function (F(int, int,...)).
template <typename F>
static auto Decouple(const F &f, enable_if_t<!is_invocable_v<F, int>> * = nullptr)
{
return [f](int v) { return Decouple( Curry(f, v) ); };
}

Все работает нормально, если передается функция с двумя аргументами:

auto f1 = Decouple(
[](int a, int b)
{ std::cout << a << " " << b << std::endl; }
);
f1(3)(4); // Outputs 3 4

Но если я добавлю больше аргументов

auto f2 = Decouple(
[](int a, int b, int c)
{ std::cout << a << " " << b << " " << c << std::endl; }
);
f(5)(6)(7);

Компиляция ломается: https://coliru.stacked-crooked.com/a/10c6dba670d17ffa

main.cpp: In instantiation of 'decltype(auto) CurryT<TFunc, TArg>::operator()(TArgs...) const [with TArgs = {int}; TFunc = main()::<lambda(int, int, int)>; TArg = int]':
main.cpp:17:26: error: no match for call to '(const main()::<lambda(int, int, int)>) (const int&, int&)'
17 | { return func(arg, args...); }

Он прерывается при создании экземпляра std::is_invocable.

Так как отладка стандартной библиотеки сложна, я создал простые версии классов признаков стандартного типа:

template <typename F> true_type check(const F &, decltype( declval<F>()(1) )* );
template <typename F> false_type check(const F &,...);
template <typename F>
struct invocable_with_int: decltype(check(declval<F>(), nullptr))
{};
template <typename F>
inline constexpr bool invocable_with_int_v = invocable_with_int<F>::value;
template<bool B>
struct my_enable_if {};
template<>
struct my_enable_if<true>
{ using type = void; };
template <bool B>
using my_enable_if_t = typename my_enable_if<B>::type;

Проблема осталась прежней https://coliru.stacked-crooked.com/a/722a2041600799b0:

main.cpp:29:73: required by substitution of 'template<class F> std::true_type check(const F&, decltype (declval<F>()(1))*) [with F = CurryT<main()::<lambda(int, int, int)>, int>]'

Он пытается разрешить вызов этой функции:

template <typename F> true_type check(const F &, decltype( declval<F>()(1) )* );

Но decltype (declval<F>()(1))*)терпит неудачу. Но не следует ли убрать эту функцию из разрешения перегрузки, потому что подстановка шаблона не удалась? Он работает, когда Decoupleвызывается в первый раз. Но при втором вызове SFINAE как бы отключается, а первый сбой подстановки шаблона дает ошибку компиляции. Существуют ли какие-либо ограничения для вторичного SFINAE? Почему рекурсивный вызов функции шаблона не работает?

Проблема воспроизводится в GCC и Clang. Так что это не ошибка компилятора.


Решение проблемы

Ваша operator()перегрузка полностью не ограничена и, следовательно, утверждает, что ее можно вызывать с любым набором аргументов. Проверяются только объявления, а не определения, чтобы определить, какую функцию вызывать при разрешении перегрузки. Если подстановка в определение не удалась, SFINAE не применяется.

Итак, ограничьте ваш operator()запрос TFuncна вызов с аргументами TArgи TArgs...в качестве аргументов.

Например:

template <typename... TArgs>
auto operator()(TArgs...args) const -> decltype(func(arg, args...))

Комментариев нет:

Отправить комментарий

Laravel Datatable addColumn returns ID of one record only

Я пытаюсь использовать Yajra Datatable для интеграции DataTable на свой веб-сайт. Я смог отобразить таблицу, но столкнулся с проблемой. В по...