Я использую tl::expected и хочу определить руководство по вычитанию для создания tl::unexpected определенного типа, который я использую в файле .cpp:
(очень упрощенный пример)
class MyError {
public:
enum ErrorType : int {
none,
type1,
type2
};
MyError (ErrorType type, const std::string& description)
{
}
};
template<typename T>
tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
Этот код компилируется без проблем с GCC (пробовал версии с 11 по 14),
но не компилируется с Clang 14 по 18. Также Clang выдает странные сообщения об ошибках (как будто синтаксис полностью сломан):
<source>:2476:35: ошибка: ожидался ')'
2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
| ^
<source>:2476:16: замечание: для соответствия этому '('
2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
| ^
<source>:2476:16: ошибка: нельзя использовать скобки при объявлении переменной с уточненным типом шаблона
2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
| ^
<source>:2476:26: ошибка: объявление переменной 'ErrorType' с уточненным типом 'tl::unexpected' требует инициализации
2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
| ^
<source>:2476:46: ошибка: ожидался ';' в конце объявления
2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
| ^
| ;
<source>:2476:46: ошибка: нельзя использовать оператор стрелки с типом
5 ошибок сгенерировано.
Компилятор вернул: 1
После замены MyError::ErrorType на int в руководстве по вычитанию:
template<typename T>
tl::unexpected (int, const T&)->tl::unexpected<MyError>;
сообщение об ошибке (почти) совершенно другое:
<source>:2476:17: ошибка: ожидался неквалифицированный идентификатор
2476 | tl::unexpected (int, const T&)->tl::unexpected<MyError>;
| ^
<source>:2476:17: ошибка: ожидался ')'
<source>:2476:16: замечание: для соответствия этому '('
2476 | tl::unexpected (int, const T&)->tl::unexpected<MyError>;
| ^
2 ошибки сгенерировано.
Компилятор вернул: 1
Тем не менее, простой пример шаблона компилируется без ошибок в Clang:
template<typename T1, typename T2>
class C
{
private:
T1 a;
T2 b;
};
template<typename T>
C (int, const T&)->C<int, const T&>;
Так что я не имею никакого представления, где может быть проблема? В моем руководстве по вычитанию, в реализации tl::expected или в clang (поскольку код компилируется и выполняется без проблем, когда компилируется GCC)
Вот ссылка на компилятор explorer для приведенного выше примера:
https://godbolt.org/z/z3Ybrx1he
Пример кода находится внизу (строка 2450 и ниже) после содержания заголовка tl::expected. Похоже, что ни clang, ни gcc не знают о std::expected, и поэтому я использую tl::expected.
Ответ
Проблема, с которой вы сталкиваетесь, связана с синтаксисом, используемым для определения проводника вывода (deduction guide
) в C++. В первом случае компилятор Clang не может разобрать синтаксис, потому что он не ожидает, что вы будете определять проводник вывода в такой форме.
Давайте подробнее разберем, как это исправить.
Проблема в вашем коде
Ваш изначальный код имеет следующий вид:
template<typename T>
tl::unexpected (MyError::ErrorType, const T&) -> tl::unexpected<MyError>;
В этом выражении tl::unexpected (MyError::ErrorType, const T&)
компилятор ожидает, что вы определяете переменную или функцию, но вы хотите задать проводник вывода. Правильный формат для определения проводника вывода должен использовать struct
или class
. Из-за этого компилятор Clang отказывается компилировать ваш код, ссылаясь на синтаксическую ошибку.
Решение
Чтобы определить проводник вывода для вашего типа tl::unexpected<MyError>
, используйте правильный синтаксис. Например:
template<typename T>
struct tl::unexpected;
template<typename T>
tl::unexpected(MyError::ErrorType, const T&) -> tl::unexpected<MyError>;
Если вы хотите сделать все более понятным и явно указать, что вы определяете проводник вывода, вы можете использовать следующий подход:
template<typename T>
tl::unexpected(MyError::ErrorType, const T&) -> tl::unexpected<MyError>;
После того, как вы задали проводник вывода правильно, убедитесь, что вы включили необходимые заголовочные файлы и используете подходящие неймспейсы.
Пример применения
Вот пример того, как может выглядеть полностью рабочий код:
#include <string>
#include <tl/expected.hpp> // Убедитесь, что вы подключили нужные заголовки
class MyError {
public:
enum ErrorType : int {
none,
type1,
type2
};
MyError(ErrorType type, const std::string& description) {
// Конструктор ошибки
}
};
// Проводник вывода для tl::unexpected
template<typename T>
tl::unexpected(MyError::ErrorType, const T&) -> tl::unexpected<MyError>;
int main() {
// Пример использования
auto unexpectedError = tl::unexpected<MyError>(MyError::type1, "Описание ошибки");
return 0;
}
Заключение
Ключевая проблема заключалась в неправильном синтаксисе при определении проводника вывода для типажей, который Clang не мог корректно разобрать. Теперь, используя правильный синтаксис, ваш код должен компилироваться как в GCC, так и в Clang без ошибок.
Если после этих исправлений у вас все еще возникают проблемы, убедитесь, что используете последнюю версию Clang и библиотеки tl::expected
, так как потенциальные проблемы могут быть связаны с несовместимостью версий.