Шаблон C++ позволяет отказаться от квалификатора ссылки const

Почему этот код компилируется? (проверено с помощью g++ и clang++)

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

В main() я использую factory() для создания пересылки на test_func(), которая принимает неконстантный ссылочный параметр. Чего я не понимаю, так это почему это не приводит к ошибке об исключении квалификатора const из аргумента.

Обратите внимание, что действительно экземпляр класса C, созданный в main(), передается без создания каких-либо копий.

#include <functional>
#include <iostream>

class C
{
public:
        C() {}

        C(const C&)
        {
                std::cout << "C copy\n";
        }
};

int test_func(C& cref)
{
        return 0;
}

template<typename Ret, typename... Arg>
std::function<Ret (const Arg&...)> factory(Ret (*func) (Arg...))
{
        return [ func ] (const Arg&... arg) -> Ret {
                return (func)(arg...);
        };
}

int main() {
        auto caller = factory(test_func);
        C c;
        caller(c);
        return 0;
}

person gnobal    schedule 29.10.2013    source источник
comment
почему Ret (*func) (Arg...), а не Ret (*func) (const Arg&...)?   -  person ForEveR    schedule 29.10.2013
comment
С Arg == C&, const Arg& (или Arg const&) == C& const& == C&.   -  person Xeo    schedule 29.10.2013
comment
@ForEveR Я не хочу ничего предполагать о типах функций, которые я перенаправляю, предположительно, чтобы заставить их работать с любой функцией (это просто уменьшенный пример того, что я пытался сделать)   -  person gnobal    schedule 29.10.2013
comment
тогда вам нужна идеальная переадресация   -  person BЈовић    schedule 29.10.2013
comment
@Xeo Я этого не знал ... почему бы вам не указать это в ответе с надлежащей ссылкой на ресурс, который это объясняет (например, стандарт C ++ или статья). Я понимаю, о чем вы говорите, но я никогда не думал, что константу можно удалить таким образом.   -  person gnobal    schedule 29.10.2013
comment
@BЈовић Можете ли вы поместить это в код? На самом деле я пробовал идеальную пересылку, но это создавало дополнительные копии аргументов, которые я не мог объяснить.   -  person gnobal    schedule 29.10.2013
comment
Как сказал @Xeo, C& const& == C&. §8.3.2/1 гласит: Cv-квалифицированные ссылки имеют неправильный формат, за исключением случаев, когда cv-квалификаторы вводятся с помощью typedef (7.1.3) или аргумента типа шаблона (14.3), и в этом случае cv -квалификаторы игнорируются.   -  person MWid    schedule 29.10.2013
comment
@MWid В конце концов я нашел это сам. Однако похоже, что тот же самый текст изменен в следующем стандартном черновике и больше не включает шаблоны: ссылки с квалификатором Cv имеют неправильный формат, за исключением случаев, когда квалификаторы cv вводятся посредством использования typedef-name (7.1.3). , 14.1) или decltype-specificer (7.1.6.2), и в этом случае квалификаторы cv игнорируются. Не знаю, что это значит, но я понимаю текущую ситуацию. Найдено здесь: isocpp.org/files/papers/N3797.pdf   -  person gnobal    schedule 29.10.2013
comment
Шаблоны @gnobal по-прежнему включены. Идентификатор параметра-типа – это имя-определения-типа (если идентификатор не следует за многоточием). Это §14.1/3.   -  person MWid    schedule 29.10.2013


Ответы (1)


Как упоминалось в комментариях, вы должны использовать идеальную переадресацию (см. презентацию Скотта Мейерса на Универсальный справочник).

В вашем случае это должно быть:

#include <functional>
#include <iostream>
#include <utility>

class C
{
public:
        C() {}

        C(const C&)
        {
                std::cout << "C copy\n";
        }
};

int test_func(const C& )
{
        return 0;
}

template<typename Ret, typename... Arg>
std::function<Ret (Arg...)> factory(Ret (*func) (Arg...))
{
        return [ func ] (Arg&&... arg) -> Ret {
                return func(std::forward<Arg>(arg)...);
        };
}

int main() {
        auto caller = factory(test_func);
        const C c;
        caller(c);
}

Обратите внимание, что я изменил C c; на const C c; в вашем main(), а также изменил файл test_func.


Если вы хотите избежать создания копий, вы должны убедиться, что функция test_func не принимает значения. Он должен приниматься по ссылке (const или non-const).

person BЈовић    schedule 29.10.2013
comment
Большое спасибо, но теперь, если я использую этот код для test_func(C c) (который принимает тип значения), он делает 4 копии C, что я не понимаю, почему и неприемлемо (я бы хотел, чтобы идеальная пересылка делала столько копий, сколько это было бы сделано, если бы я просто сам вызвал функцию) - person gnobal; 29.10.2013
comment
@gnobal У вас нет конструктора перемещения, поэтому он вызывает конструктор копирования - person BЈовић; 29.10.2013
comment
Я понимаю, но почему 4 копии? Если бы он был идеально перенаправлен, я бы ожидал ровно 1 копию, как если бы я сам позвонил test_func(c). - person gnobal; 29.10.2013
comment
@gnobal У меня была ошибка. Сейчас только 3 экземпляра. 1-й, чтобы перейти к функциональному объекту. 2-й перейти к функц. 3-й перейти к test_func - person BЈовић; 29.10.2013
comment
@gnobal есть идеальная пересылка, но ни одна фиксированная подпись не может быть идеальной. Так что промежуточный этап выполнен отлично. Специально написанный набор override_function_set, который предоставляет экспоненциальное число operator() перегрузок, может идеально выполнять переадресацию (2^n перегрузок для n аргументов по значению). Писать было бы забавно, но редко практично. - person Yakk - Adam Nevraumont; 29.10.2013
comment
@BЈовић и Якк Спасибо. Думаю, совершенство в совершенной пересылке смутило меня. В конце концов, чтобы создать наименьшее количество копий, кажется, что решение const& является правильным для меня, потому что оно не создает никаких дополнительных копий. Я все еще пытаюсь выяснить, почему исходный код, опубликованный в вопросе, строится (помогает комментарий Xeo) - person gnobal; 29.10.2013
comment
@BЈовић: Я на 100% уверен, что Скотт Мейерс придумал термин «универсальная ссылка», но я считаю, что это не он придумал термин «идеальная переадресация». - person Cassio Neri; 29.10.2013
comment
@CassioNeri Я не уверен, о чем я думал, когда писал эту чепуху. Исправлено сейчас. Спасибо - person BЈовић; 29.10.2013