2015-01-01 1 views
3

Я думал о неявных шаблонах C++ 14, и я пытаюсь объявить функцию для соответствия определенному типу аргумента (SFINAE и черты все еще дают мне головные боли). Я не знаю, как объяснить, что я хочу, но я пытаюсь сделать Y combinator (просто посмотреть, возможно ли это, а не для производства).Как создать комбинатор Y на основе SFINAE в C++?

Я пытаюсь объявить функцию:

template<typename T> 
my_traits<T>::return_type Y(T t) { 
    // ... 
}; 

Такое, что T является функцией (или функтор), который соответствует

std::function<R(F, Args...)> 

// where F (and above return_type) will be 
std::function<R(Args...)> 

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

Использование я хочу добиться:

auto fib = [](auto myself, int x) { 
    if(x < 2) 
    return 1; 
    return myself(x - 1) + myself(x - 2); 
}; 

// The returned type of fib should be assignable to std::function<int(int)> 

Я не был в состоянии принять тип возвращаемого T типа (из-за перегруженного operator()). То, что я пытаюсь сделать, возможно? Как я могу это сделать?


Edit:

Видя это с другой точки зрения, я пытаюсь сделать эту работу:

struct my_functor { 
    template<typename T> 
    char operator() (T t, int x, float y) { /* ... */ }; 
}; 

template<typename T> 
struct my_traits { 
    typedef /* ... */ result_type; 

    /* ... */ 
}; 

// I want this to be std::function<char(int, float)>, based on my_functor 
using my_result = 
my_traits<my_functor>::result_type; 
+0

Применение исправления в предыдущем комментарии, возвращаемый тип вашего 'fib' выводится на' int'.Если я правильно понимаю Y-комбинаторы, они не возвращают функцию, как вы предлагаете, они возвращают элементы в домене их параметра. Я что-то упускаю? – ThomasMcLeod

+0

'сам (x - 1) + себя (x - 2)', как я его написал. Это идея. Lambda будет генерировать 'template int operator() (T, int)', я хочу получить 'int (int)' из него. – paulotorrens

+0

@ThomasMcLeod, Y-комбинатор принимает нерекурсивную функцию (первый аргумент которой является ее версией без первого аргумента) и возвращает рекурсивную функцию. Поэтому я пытаюсь вывести 'int (int)' из 'int (T, int)'. – paulotorrens

ответ

2

Невозможно в выводе типа C++ 14 вывести int(int) из int(T, int) по желанию OP.

Однако мы можем скрыть первый параметр результата, используя следующий подход. struct YCombinator создается экземпляром объекта нерекурсивной функции, первым аргументом которого является сама версия без первого аргумента. YCombinator предоставляет оператор вызова, который принимает аргументы нерекурсивной функции, а затем возвращает свой член объекта функции после подстановки себя для первого аргумента. Этот метод позволяет программисту избежать беспорядочности вызовов myself(myself, ...) в определении рекурсивной функции.

template<typename Functor> 
struct YCombinator 
{ 
    Functor functor; 

    template<typename... Args> 
    decltype(auto) operator()(Args&&... args) 
    { 
     return functor(*this, std::forward<Args>(args)...); 
    } 
}; 

make_YCombinator утилита шаблона позволяет обтекаемой схемы использования. Этот запуск компиляции выполняется в GCC 4.9.0.

template<typename Functor> 
decltype(auto) make_YCombinator(Functor f) { return YCombinator<Functor> { f }; } 

int main() 
{ 
    auto fib = make_YCombinator([](auto self, int n) -> int { return n < 2 ? 1 : self(n - 1) + self(n - 2); }); 

    for (int i = 0; i < 10 ; ++i) 
     cout << "fib(" << i << ") = " << fib(i) << endl; 

    return 0; 
} 

Поскольку нерекурсивна функция не определена в момент времени, что рекурсивная функция определена, в общем случае рекурсивная функция должна иметь явный тип возвращаемого значения.

Edit:

Однако, это может быть возможно для компилятора вывести тип возвращаемого в некоторых случаях, если программист берет на себя, чтобы указать тип возвращаемого значения рекурсивной функции перед использованием нерекурсивно функция. В то время как выше конструкция требует явного возвращаемого типа, в следующем GCC 4.9.0 не имеет никаких проблем выведению типа возвращаемый:

auto fib = make_YCombinator([](auto self, int n) { if (n < 2) return 1; return self(n - 1) + self(n - 2); }); 

Чтобы закрепить это вниз только немного дальше, вот цитата из проекта C + +14 стандарт на возвращение типа удержания [7.1.6.4.11]:

Если тип объекта с undeduced типа заполнитель необходим определить тип выражения, программа плохо сформирована. Однако, как только оператор возврата был замечен в функции, возвращаемый тип , выведенный из этого оператора, может использоваться в остальной части функции , в том числе в других операторах return. [Пример:

auto n = n; // error, n’s type is unknown 
auto f(); 
void g() { &f; } // error, f’s return type is unknown 
auto sum(int i) { 
if (i == 1) 
    return i; // sum’s return type is int 
else 
    return sum(i-1)+i; // OK, sum’s return type has been deduced 
} 

-end пример]

+1

Мне очень нравится этот ответ. – ildjarn

3

Это действительно Hacky подход, и имеет серьезные ограничения, но вот он:

Во-первых, нам нужен класс, который претендует на поддержку всех возможных операций (насколько это возможно), таких как fake_anything класс. Обратите внимание, что это не идеально, так как минимум . и :: не будут работать. Чтобы подделать функтор, мы даем ему оператор вызова функции:

template<class... Ts> fake_anything operator()(Ts&&...) const; 

Зная, что лямбда имеет только один operator(), и что operator() имеет только один параметр шаблона позволяет извлекать свою подпись с decltype(&T::operator()<fake_anything>). Для этого необходимо указать тип возврата лямбда; он не может использовать вычет, так как в противном случае выведенные типы возвращаемости, вероятно, будут конфликтовать.

Наконец, мы можем получить другие аргументы для лямбда и типа возвращаемого значения с использованием стандартного частичной специализации подхода:

template<class T> 
struct extract_signature; 

template<class T, class R, class FA, class...Args> 
struct extract_signature<R (T::*)(FA, Args...)> { 
    static_assert(std::is_same<fake_anything, std::decay_t<FA>>::value, "Unexpected signature"); 
    using type = std::function<R(Args...)>; 
}; 

template<class T, class R, class FA, class...Args> 
struct extract_signature<R (T::*)(FA, Args...) const> { 
    static_assert(std::is_same<fake_anything, std::decay_t<FA>>::value, "Unexpected signature"); 
    using type = std::function<R(Args...)>; 
}; 
// other cv- and ref-qualifier versions omitted - not relevant to lambdas 
// we can also static_assert that none of Args is fake_anything, or reference to it, etc. 

И добавить шаблон псевдонима, чтобы скрыть все уродство хака:

template<class T> 
using signature_t = typename extract_signature<decltype(&T::template operator()<fake_anything>)>::type; 

И, наконец, мы можем проверить, что

static_assert(std::is_same<signature_t<decltype(fib)>, 
          std::function<int(int)>>::value, "Oops"); 

Demo.

Ограничения:

  • должно быть явно указано Тип возврата operator(). Вы не можете использовать автоматический вывод типа возврата, если все операторы return не возвращают один и тот же тип независимо от типа возвращаемого значения функтора.
  • Подделка очень несовершенна.
  • Это работает только для operator(): template<class T> R operator()(T, argument-types...) с или без const, где первый параметр - T или ссылка на возможную квалификацию cv T.
+0

не так, что когда вы создаете экземпляр 'operator()' лямбды '' с помощью 'fake_type' -' decltype (& T :: template operator() ) ', то с явно указанным типом возврата компилятор должен не использовать ' t экземпляр всего тела 'operator()', только декларация, [как здесь?] (http://coliru.stacked-crooked.com/a/e9407db9346bdb10) –

+0

@PiotrS. GCC [создает экземпляр тела] (http://coliru.stacked-crooked.com/a/95bd106f42bb173b); Я не уверен, какой компилятор прав. –

+0

ОК спасибо, по крайней мере, несовершенство 'fake_anything' не влияет на Clang –

Смежные вопросы