Как сделать эти параметры std::function однозначными?

Следующие перегрузки функций неоднозначны при прохождении лямбды. я узнал что std::function может быть создан из большинства вызываемых типов, даже если их сигнатура не совпадает. Таким образом, компилятор не может сказать, какую функцию использовать.

template <typename T> void each(std::function<void(T)> iterator);
template <typename T> void each(std::function<void(T, id)> iterator);
template <typename T> void each(std::function<void(T&)> iterator);
template <typename T> void each(std::function<void(T&, id)> iterator);

Здесь есть несколько похожих вопросов, но ни один из них не может решить мою проблему. Как я могу устранить неоднозначность без изменения использования? Более того, в то время я должен явно упомянуть тип шаблона. Это можно обойти?

2 ответа

Решение

Половина этого - LWG выпуск 2132, удаление std::functionконструктор из разрешения перегрузки, если только аргумент не может быть вызван для указанных типов аргументов. Для этого требуется поддержка выражения SFINAE, которого нет в VC++.

Другая половина проблемы - разрешение перегрузки:

#include<functional>
#include<iostream>
struct id {};
template <typename T> void each(std::function<void(T)> ){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <typename T> void each(std::function<void(T, id)> ){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <typename T> void each(std::function<void(T&)> ){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <typename T> void each(std::function<void(T&, id)> ){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
int main() {
    each<int>([](int, id){});
}

С библиотекой, которая реализует LWG2132, этот код печатает, возможно, на удивление:

void each(std::function<void(T&, id)>) [with T = int]

Зачем? Во-первых, можно построить std::function<void(T&, id)> от [](int, id){}, В конце концов, последний может быть вызван с lvalue типа int просто хорошо.

Во-вторых, в

template <typename T> void each(std::function<void(T, id)>);
template <typename T> void each(std::function<void(T&, id)>);

Второй более специализирован, чем первый, по правилам частичного упорядочения для шаблонов функций, поэтому он всегда выбирается по разрешению перегрузки.


Возможное решение - извлечь сигнатуру, манипулируя типом лямбды. operator ():

template<class T>
struct mem_fn_type;
template<class R, class C, class... T>
struct mem_fn_type<R(C::*)(T...)> {
    using type = std::function<R(T...)>;
};
template<class R, class C, class... T>
struct mem_fn_type<R(C::*)(T...) const> {
    using type = std::function<R(T...)>;
};

// optional extra cv-qualifier and ref-qualifier combos omitted
// since they will never be used with lambdas    

// Detects if a class is a specialization of std::function
template<class T>
struct is_std_function_specialization : std::false_type {};

template<class T>
struct is_std_function_specialization<std::function<T>> : std::true_type{};

// Constrained to not accept cases where T is a specialization of std::function,
// to prevent infinite recursion when a lambda with the wrong signature is passed
template<class T>
typename std::enable_if<!is_std_function_specialization<T>::value>::type each(T func) {
    typename mem_fn_type<decltype(&T::operator())>::type f = func;
    each(f);
}

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

Более того, в то время я должен явно упомянуть тип шаблона. Это можно обойти?

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

Функция, подобная вашей, будет применяться к шаблону посетителя, поэтому я приведу вам пример, использующий это:

class collection {
    std::vector<int> data; // to be visited
public:
    void visit(std::function<void(int)> visitor) // single overload
    {
        std::for_each(std::begin(data), std::end(data), visitor);
    }
};

void complex_visitor(int element, double EXTRA, char* PARAMETERS, bool HERE);

Код клиента:

collection c;
char* data = get_some_data();
bool a = false;
c.visit( [&](int x) { complex_visitor(x, .81, data, a); } );

В этом примере именно в последней строке (т. Е. В коде клиента) вы решаете, как подключить несоответствующего посетителя, а не в интерфейсе интерфейса. collection учебный класс.

Другие вопросы по тегам