Соответствие заголовка функции C ++: как работает сопоставление, когда задействованы const и шаблоны? - PullRequest
2 голосов
/ 11 апреля 2019

У меня была шаблонная функция, которую я хотел вызвать.Это (урезанная версия) заголовка:

template <typename Item>
void print (shared_ptr<const MyContainer<Item>> stuff, ostream& out)

, который я пытался вызвать такой строкой:

print (make_shared<MyContainer<int>>(42), cerr);

Но компилятор жаловался, что не быломатч.Что меня смущает, так это то, что несоответствие const не является проблемой, потому что, если я переименую свою функцию, чтобы пропустить шаблон, он работает:

void print (shared_ptr<const MyContainer<int>> stuff, ostream& out)  //matches!

С другой стороны, если я опускаю constness, шаблонная версия работает:

template <typename Item>
void print (shared_ptr<MyContainer<Item>> stuff, ostream& out)  //also matches!

Но я должен быть в состоянии написать функцию над константными объектами и передать ей неконстантное значение (которое функция затем просто не изменит), верно?В самом деле, если я вернусь к неуправляемым указателям, соответствующий старый способ записи заголовка был бы

template <typename Item>
void print (const MyContainer<Item>* stuff, ostream& out)

, и действительно, тогда вызов

print (new MyContainer<int>(42), cerr);  //yet another match!

еще разхорошо.

Итак, что же это за конкретный набор shared_ptr, шаблонов и const, который приводит к тому, что компилятор не может найти соответствующую функцию?(Запуск g ++ 8.2.1 и clang ++ 7.0.1, похоже, дают тот же результат.)

Ответы [ 2 ]

2 голосов
/ 11 апреля 2019

Что касается константности pointee, std::shared_ptr ведет себя немного иначе, чем необработанные указатели.

A std::shared_ptr<T> - это не то же самое, что std::shared_ptr<const T>. Это даже не так совместимо, чтобы разрешить неявное преобразование. (Сообщение об ошибке в ответе Дэниелс говорит об этом буквально.)

Он не работает по той же причине, что и в следующем (встречном) примере:

template <typename T>
struct ContainerT {
  T a;
  ContainerT(T a): a(a) { }
  ContainerT(const ContainerT&) = default;
  ContainerT& operator=(const ContainerT&) = default;
};

int main()
{
  ContainerT<int> a(42);
  ContainerT<const int> b(a);
  return 0;
}

Выход:

g++ (GCC) 8.2.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

main.cpp: In function 'int main()':
main.cpp:15:28: error: no matching function for call to 'ContainerT<const int>::ContainerT(ContainerT<int>&)'
   ContainerT<const int> b(a);
                            ^
main.cpp:8:3: note: candidate: 'constexpr ContainerT<T>::ContainerT(const ContainerT<T>&) [with T = const int]'
   ContainerT(const ContainerT&) = default;
   ^~~~~~~~~~
main.cpp:8:3: note:   no known conversion for argument 1 from 'ContainerT<int>' to 'const ContainerT<const int>&'
main.cpp:7:3: note: candidate: 'ContainerT<T>::ContainerT(T) [with T = const int]'
   ContainerT(T a): a(a) { }
   ^~~~~~~~~~
main.cpp:7:3: note:   no known conversion for argument 1 from 'ContainerT<int>' to 'int'

Демонстрация в реальном времени на coliru


В случае std::shared_ptr существует способ обойти эту проблему
& Rarr; std::const_pointer_cast можно использовать:

#include <iostream>
#include <memory>

template <typename T>
struct ContainerT {
  T a;
  ContainerT(T a): a(a) { }
};

template <typename T>
void print(std::shared_ptr<const ContainerT<T>> ref, std::ostream &out)
{
  out << "print: '" << ref->a << "'\n";
}

int main()
{
  print(std::make_shared<const ContainerT<int>>(42), std::cout);
  print(std::const_pointer_cast<const ContainerT<int>>(std::make_shared<ContainerT<int>>(42)), std::cout);
  return 0;
}

Выход:

print: '42'
print: '42'

Демонстрация в реальном времени на coliru


Для удобства приведение констант может быть выполнено в другом шаблоне функции:

#include <iostream>
#include <memory>

template <typename T>
struct ContainerT {
  T a;
  ContainerT(T a): a(a) { }
};

template <typename T>
void print(std::shared_ptr<const ContainerT<T>> ref, std::ostream &out)
{
  out << "print const: '" << ref->a << "'\n";
}

template <typename T>
void print(std::shared_ptr<ContainerT<T>> ref, std::ostream &out)
{
  out << "print non-const: ";
  print(std::const_pointer_cast<const ContainerT<T>>(ref), out);
}

int main()
{
  print(std::make_shared<const ContainerT<int>>(42), std::cout);
  print(std::make_shared<ContainerT<int>>(42), std::cout);
  return 0;
}

Выход:

print const: '42'
print non-const: print const: '42'

Демонстрация в реальном времени на coliru

1 голос
/ 11 апреля 2019

Вот упрощенный код:

template <typename T>
void f(std::shared_ptr<const std::vector<T>>) { }

void g(std::shared_ptr<const std::vector<int>>) { }

int main() {
  f(std::make_shared<std::vector<int>>()); // ERROR
  g(std::make_shared<std::vector<int>>()); // OK
}

Чтобы понять, что происходит, прочитайте сообщение об ошибке, например, напечатанное g ++:

...
note:   template argument deduction/substitution failed:
note:   types 'const std::vector<T>' and 'std::vector<int>' have incompatible cv-qualifiers

В нем говорится, чтопроблема с выводом / заменой аргумента шаблона.Правила C ++, по-видимому, не допускают такого типа дедукции.(Если у меня будет время, я попытаюсь найти соответствующую часть Стандарта).


Однако вы можете пропустить вывод аргумента шаблона, указав явный аргумент шаблона:

f<int>(std::make_shared<std::vector<int>>()); // OK
...