эффекты `const` в качестве аргумента шаблона для конструктора перемещения - PullRequest
1 голос
/ 29 июня 2019

Учитывая следующие случаи, каков эффект добавления const как части аргумента шаблона?

Случай 1: void func() принимает параметр типа T при копировании

#include <iostream>
#include <boost/type_index.hpp>

template<typename T>
void func(T val_)
{
    using boost::typeindex::type_id_with_cvr;
    std::cout<< "type of `val_` is `" << type_id_with_cvr< decltype(val_) >().pretty_name() << "`" <<std::endl;
}

int main()
{
    int *f = new int(2);  

    func<int>(2);                // type of `val_` is `int` - OK
    func<int*>( f );             // type of `val_` is `int*` - OK
    func<const int*>( f );       // type of `val_` is `int const*` - OK
    func<const int* const>( f ); // type of `val_` is `int const* const` - OK
}

Нет вопросов для первого случая ... Понятно, что происходит.

Случай 2: void func() принимает параметр типа T по ссылке

#include <iostream>
#include <boost/type_index.hpp>

template<typename T>
void func(T &val_)
{
    using boost::typeindex::type_id_with_cvr;
    std::cout<< "type of `val_` is `" << type_id_with_cvr< decltype(val_) >().pretty_name() << "`" <<std::endl;
}

int main()
{
    int *f = new int(2);  

    func<int>(2);                // error - OK
    func<int*>( f );             // type of `val_` is `int*&` - OK
    func<const int*>( f );       // error: no known conversion from 'int *' to 'const int *&' - OK
    func<const int* const>( f ); // type of `val_` is `int const* const&` - ???
}

Случай 3: void func() принимает параметр типа T путем переадресации / универсальной ссылки

#include <iostream>
#include <boost/type_index.hpp>

template<typename T>
void func(T &&val_)
{
    using boost::typeindex::type_id_with_cvr;
    std::cout<< "type of `val_` is `" << type_id_with_cvr< decltype(val_) >().pretty_name() << "`" <<std::endl;
}

int main()
{
    int *f = new int(2);  

    func<int>(2);                // type of `val_` is `int&&` - OK
    func<int*>( f );             // error: no known conversion from 'int *' to 'int *&&' - OK
    func<const int*>( f );       // type of `val_` is `int const*&&` - ???
    func<const int* const>( f ); // type of `val_` is `int const* const&&` - ???
}

Вопрос № 1: В случае № 2, почему func<const int* const>( f ); принимается, просто добавив rhs const?

Вопрос № 2: В случае № 3, почему два последних вызова на func принимаются только путем добавления const?

Вопрос № 3: В случае № 3 преимущество использования ссылки пересылки состоит в том, что если аргумент функции является lvalue, то тип val_ будет ссылкой lvalue. Если аргумент является rvalue, тогда тип val_ будет rvalue. Таким образом, пропуск f, который является lvalue, должен составить T int*&. Это действительно тот случай, когда вы не указываете аргумент шаблона int* при вызове функции. Однако при указании аргумента шаблона int* результатом будет int*&&. Почему?

Ответы [ 2 ]

1 голос
/ 29 июня 2019

В случае № 2, первый вопрос должен быть: почему func<const int *>(f) плохо?В конце концов, обычно вы можете использовать int * там, где требуется const int *.Причина, по которой он терпит неудачу, заключается в том, что даже если int * является const int * (то есть вещь, которую вы можете разыменовать, чтобы получить int, это вещь, которую вы можете разыменовать, чтобы получить const int), ссылка на int * не является ссылкой на const int *, потому что вы можете назначить "через" его.Если вам было разрешено звонить func<const int *>(f), то другая версия func могла бы, например, назначить что-то, что действительно a const int * на f, после чего вы сможете изменить егоуказывает на, что было бы Плохо.

И теперь должно быть очевидно, почему второй const исправляет его: вы не можете изменить значение f, передав его по ссылке const.

Дело № 3 несколько похоже.Опять же, первый вопрос, который нужно задать, заключается не в том, почему ваши третье и четвертое дела работают, а в том, почему ваше второе дело не удалось.Сбой из-за того, что вы не можете получить ссылку на rvalue (&&) на lvalue (вещь с именем, например f).Почему бы и нет?Потому что одна из причин этого заключается в том, чтобы иметь возможность получать различное поведение в зависимости от того, является ли передаваемая вещь временной (и, следовательно, безопасной для клюва) или нет, и способ, которым это делается, состоит в том, чтобы запретитьпреобразование lvalue типа T в rvalue ссылку типа T &&.(Так что тогда вы можете написать одну функцию, которая принимает T &, а другую - T &&, и заставить последнюю делать более эффективную штуковину.)

Но почти все, что вы делаете с этим lvalueперестанет быть lvalue и снова сделает вызов func легальным.Например, вы можете добавить 0 к нему.Или вы могли бы привести его к другому типу.И, ага!, Это то, что происходит в вашем третьем и четвертом примерах в случае № 3: то, что передается в func, фактически не само по себе f, а что-то вроде const_cast<const int *>(f), и это больше не lvalue ивсе хорошо.

1 голос
/ 29 июня 2019

Тип f равен int*. Только int*& или int* const& могут связываться непосредственно с этим.

Для случая # 2, int* и int const* - это разные типы, поэтому ссылка на int cont* не может привязываться напрямую к f. Существует преобразование из int* в int const*, но lvalue-reference-to-non-const не может привязаться к результирующему объекту, потому что это rvalue. Lvalue-reference-to-const может связываться с rvalue, хотя, так как существует неявное преобразование из int* в int const* const, когда вы добавляете const к типу временного int const* const может быть создан, и ссылка может связываться с ним.

По этой же причине следующие работы:

void foo(const std::string& str) {}
int main() { foo("hello"); }

Хотя следующее не так:

void foo(std::string& str) {}
int main() { foo("hello"); }

Для случая № 3 int* и int const* - это разные типы. Поскольку существуют неявные преобразования из int* в int const* и int const* const, создаются временные копии. Эти временные копии являются значениями rvalue, поэтому ссылки на rvalue могут без проблем с ними связываться Когда вы используете int* в качестве параметра шаблона, тип уже совпадает, поэтому временная копия не создается. Поскольку f является lvalue, ссылка на rvalue не может быть привязана к нему, и вы получаете ошибку.


Чтобы ответить на ваш последний вопрос, который вы редактировали: пересылка ссылок возникает только в том случае, если вы разрешаете вывод параметров шаблона. Это правила вычета параметров шаблона и свертывания ссылок, которые заставляют их работать, поэтому вы никогда не получите ссылку для пересылки, если укажете явные параметры шаблона.

Например, задан следующий шаблон функции:

template <typename T>
void foo(T&& obj) {}

Если вы передадите lvalue типа int (т.е. foo(some_int_var)), T будет выведено как int&, поэтому тип obj будет int& &&. После применения правил свертывания ссылок он сворачивается в int&: lvalue-reference-to-int. С другой стороны, если вы передадите rvalue типа int (то есть foo(42)), T будет выведено как int, поэтому тип obj будет int&&: rvalue-reference -в-инт.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...