Оператор преобразования C++ в chrono:: duration-работает с c++17, но не C++14 или меньше
Следующий код компилируется с помощью gcc 7.1.0 с набором C++17, но не компилируется с набором C++14 (или Visual Studio 2017). Его легко воспроизвести на Wandbox .
Что нужно сделать, чтобы заставить его работать с C++11/14?
#include <iostream>
#include <chrono>
int main()
{
struct Convert
{
operator std::chrono::milliseconds()
{
std::cout << "operator std::chrono::milliseconds" << std::endl;
return std::chrono::milliseconds(10);
}
operator int64_t ()
{
std::cout << "operator int64_t" << std::endl;
return 5;
}
};
Convert convert;
std::chrono::milliseconds m(convert);
std::cout << m.count() << std::endl;
int64_t i(convert);
std::cout << i << std::endl;
}
2 ответа:
Давайте начнем с того, почему это не работает в C++14. Есть два соответствующих к'Тора для
std::chrono::duration
(которыйstd::chrono::milliseconds
имеет псевдоним):duration( const duration& ) = default; template< class Rep2 > constexpr explicit duration( const Rep2& r );
Шаблонный гораздо лучше подходит для аргумента типа
Convert
. Но он будет участвовать в разрешении перегрузки только в том случае, еслиRep2
(a.k.AConvert
) неявно преобразуется в тип представленияstd::chrono::duration
. Дляmilliseconds
, то естьlong
на Wandbox. Ваш оператор преобразованияint64_t
делает это неявное преобразование возможным.Но вот в чем загвоздка. Проверка этого неявного преобразования не учитывает CV-квалификаторы функции-члена преобразования. Так что перегрузка выбрана, но она принимает по ссылке const. И ваш определяемый пользователем оператор преобразования не является
Так как же ее разрешить? Два способа:const
квалифицированным! @Galik отметил это в комментариях к вашему посту. Как такое преобразование не внутри конструктор изmilliseconds
.И, наконец, почему это работает в C++17? Это была бы гарантированная копия Элизии. Поскольку ваш
Отметьте оператор преобразования
const
. Тот однако мы выберем преобразование вint64_t
как дляm
, так и дляi
.Отметьте преобразование в
int64_t
какexplicit
. Теперь шаблонная перегрузка не будет участвовать в разрешении перегрузки дляm
.Convert
имеет преобразование вstd::chrono::milliseconds
, он используется для инициализацииm
напрямую. Мелочи его включают в себя даже не нужно выбирать конструктор копирования, просто чтобы элайд это потом.
Это довольно интересный случай. Причина, по которой он не компилируется на C++14, правильно объяснена StoryTeller и, возможно, является дефектом LWG-требование к конструктору преобразования заключается в том, что
Rep2
конвертируется вrep
, но тело этого конструктора пытается преобразоватьRep2 const
вrep
- и в конкретном примере в OP это неправильно сформировано. Теперь это LWG 3050.В C++17, однако, ни одно из соответствующих, артикулированных правил в стандарты изменились. Прямая инициализация (например,
std::chrono::milliseconds m(convert);
) все еще просто рассматривает конструкторы, и лучшим соответствием из разрешения перегрузки среди конструкторов по-прежнему был бы тот же самый дефектный конструктор преобразования, который заставляет программу не компилироваться в C++14.Однако , есть нерешенный ключевой вопрос, который и gcc, и clang, по-видимому, решили реализовать, несмотря на то, что еще нет формулировки для него. Рассмотрим:
struct A { A(); A(const A&) = delete; }; struct B { operator A(); }; B b; A a1 = b; // OK A a2(b); // ?
Согласно правилам языка сегодня, копирование-инициализация из
Еще один мотивирующий пример:b
в порядке, мы используем функцию преобразования. Но прямая инициализация изb
не подходит, нам придется использовать конструктор удаленной копииA
.Здесь, опять же, мы должны были бы пройти черезstruct Cat {}; struct Dog { operator Cat(); }; Dog d; Cat c(d);
Cat(Cat&& )
- который в данном случае хорошо сформирован, но препятствует элиминации копирования из-за временной материализации.Таким образом, предложенное разрешение этот вопрос заключается в рассмотрении как конструкторов , так и функций преобразования для прямой инициализации. В обоих примерах здесь и в Примере OP это дало бы "ожидаемое" поведение - прямая инициализация просто использовала бы функцию преобразования как лучшее соответствие. И gcc, и clang используют этот маршрут в режиме C++17, поэтому примеры компилируются сегодня.