Я пытался специализироваться std::formatter<T>
на базовом пользовательском типе Vector3D
, но получаю ошибку компилятора. Вот минимальный воспроизводимый пример:
vector.h
определяет класс Vector3D
и специализацию std::formatter<Vector3D>
, но объявляет только его функцию format
:
#ifndef VECTOR_H
#define VECTOR_H
#include <format>
using T = float;
/* Define `Vector3D` */
struct Vector3D {
T x, y, z;
Vector3D() {}
Vector3D(T x_, T y_, T z_) : x{x_}, y{y_}, z{z_} {}
};
/* Declare specialization of `std::formatter` for `Vector3D<T>` */
template <>
struct std::formatter<Vector3D> : public std::formatter<std::string> {
auto format(const Vector3D &item, std::format_context &format_context) const;
};
#endif
vector.cpp
включает vector.h
и определяет std::formatter<Vector3D>::format()
:
#include "base/vector.h"
/* Define `format` function in the specialization of `std::formatter` for `Vector3D<T>` */
auto std::formatter<Vector3D>::format(
const Vector3D &item,
std::format_context &format_context
) const {
return std::format_to(format_context.out(), "({}, {}, {})", item.x, item.y, item.z);
}
Наконец, main.cpp
пытается std::format
получить значение типа Vector3D
:
#include "base/vector.h"
int main()
{
std::format("{}\n", Vector3D(1, 2, 3));
}
Однако компиляция дает огромную ошибку компилятора:
/main.cpp: In function ‘int main()’:
/main.cpp:5:43: warning: ignoring return value of ‘std::string std::format(format_string<_Args ...>, _Args&& ...) [with _Args = {Vector3D}; string = __cxx11::basic_string<char>; format_string<_Args ...> = basic_format_string<char, Vector3D>]’, declared with attribute ‘nodiscard’ [-Wunused-result]
5 | std::format("{}\n", Vector3D(1, 2, 3));
| ^
In file included from vectors.h:4,
from /main.cpp:1:
/usr/include/c++/13/format:3742:5: note: declared here
3742 | format(format_string<_Args...> __fmt, _Args&&... __args)
| ^~~~~~
/usr/include/c++/13/format: In instantiation of ‘static std::__format::_Arg_store<_Context, _Args>::_Element_t std::__format::_Arg_store<_Context, _Args>::_S_make_elt(_Tp&) [with _Tp = Vector3D; _Context = std::basic_format_context<std::__format::_Sink_iter<char>, char>; _Args = {std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::handle}; _Element_t = std::__format::_Arg_store<std::basic_format_context<std::__format::_Sink_iter<char>, char>, std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::handle>::_Element_t]’:
/usr/include/c++/13/format:3281:23: required from ‘std::__format::_Arg_store<_Context, _Args>::_Arg_store(_Tp& ...) [with _Tp = {Vector3D}; _Context = std::basic_format_context<std::__format::_Sink_iter<char>, char>; _Args = {std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::handle}]’
/usr/include/c++/13/format:3330:14: required from ‘auto std::make_format_args(_Args&& ...) [with _Context = basic_format_context<__format::_Sink_iter<char>, char>; _Args = {Vector3D&}]’
/usr/include/c++/13/format:3743:61: required from ‘std::string std::format(format_string<_Args ...>, _Args&& ...) [with _Args = {Vector3D}; string = __cxx11::basic_string<char>; format_string<_Args ...> = basic_format_string<char, Vector3D>]’
/main.cpp:5:16: required from here
/usr/include/c++/13/format:3270:38: error: no matching function for call to ‘std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::basic_format_arg(Vector3D&)’
3270 | basic_format_arg<_Context> __arg(__v);
| ^~~~~
/usr/include/c++/13/format:3025:9: note: candidate: ‘template<class _Tp> requires __formattable_with<_Tp, _Context, typename _Context::formatter_type<typename std::remove_const<_Tp>::type>, std::basic_format_parse_context<typename _Context::char_type> > std::basic_format_arg<_Context>::basic_format_arg(_Tp&) [with _Context = std::basic_format_context<std::__format::_Sink_iter<char>, char>]’
3025 | basic_format_arg(_Tp& __v) noexcept
| ^~~~~~~~~~~~~~~~
/usr/include/c++/13/format:3025:9: note: template argument deduction/substitution failed:
/usr/include/c++/13/format:3025:9: note: constraints not satisfied
/usr/include/c++/13/format: In substitution of ‘template<class _Tp> requires __formattable_with<_Tp, _Context, typename _Context::formatter_type<typename std::remove_const<_Tp>::type>, std::basic_format_parse_context<typename _Context::char_type> > std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::basic_format_arg(_Tp&) [with _Tp = std::basic_format_context<std::__format::_Sink_iter<char>, char>]’:
/usr/include/c++/13/format:3270:31: required from ‘static std::__format::_Arg_store<_Context, _Args>::_Element_t std::__format::_Arg_store<_Context, _Args>::_S_make_elt(_Tp&) [with _Tp = Vector3D; _Context = std::basic_format_context<std::__format::_Sink_iter<char>, char>; _Args = {std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::handle}; _Element_t = std::__format::_Arg_store<std::basic_format_context<std::__format::_Sink_iter<char>, char>, std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::handle>::_Element_t]’
/usr/include/c++/13/format:3281:23: required from ‘std::__format::_Arg_store<_Context, _Args>::_Arg_store(_Tp& ...) [with _Tp = {Vector3D}; _Context = std::basic_format_context<std::__format::_Sink_iter<char>, char>; _Args = {std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::handle}]’
/usr/include/c++/13/format:3330:14: required from ‘auto std::make_format_args(_Args&& ...) [with _Context = basic_format_context<__format::_Sink_iter<char>, char>; _Args = {Vector3D&}]’
/usr/include/c++/13/format:3743:61: required from ‘std::string std::format(format_string<_Args ...>, _Args&& ...) [with _Args = {Vector3D}; string = __cxx11::basic_string<char>; format_string<_Args ...> = basic_format_string<char, Vector3D>]’
/main.cpp:5:16: required from here
/usr/include/c++/13/format:2226:13: required for the satisfaction of ‘__formattable_with<_Tp, _Context, typename _Context::formatter_type<typename std::remove_const<_Tp>::type>, std::basic_format_parse_context<typename _Context::char_type> >’ [with _Tp = Vector3D; _Context = std::basic_format_context<std::__format::_Sink_iter<char>, char>]
/usr/include/c++/13/format:2228:7: in requirements with ‘const _Formatter __cf’, ‘_Tp&& __t’, ‘_Context __fc’ [with _Formatter = std::formatter<Vector3D, char>; _Tp = Vector3D; _Context = std::basic_format_context<std::__format::_Sink_iter<char>, char>]
/usr/include/c++/13/format:2230:20: note: the required expression ‘__cf.format(__t, __fc)’ is invalid
2230 | { __cf.format(__t, __fc) } -> same_as<typename _Context::iterator>;
| ~~~~~~~~~~~^~~~~~~~~~~
/usr/include/c++/13/format: In instantiation of ‘static std::__format::_Arg_store<_Context, _Args>::_Element_t std::__format::_Arg_store<_Context, _Args>::_S_make_elt(_Tp&) [with _Tp = Vector3D; _Context = std::basic_format_context<std::__format::_Sink_iter<char>, char>; _Args = {std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::handle}; _Element_t = std::__format::_Arg_store<std::basic_format_context<std::__format::_Sink_iter<char>, char>, std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::handle>::_Element_t]’:
/usr/include/c++/13/format:3281:23: required from ‘std::__format::_Arg_store<_Context, _Args>::_Arg_store(_Tp& ...) [with _Tp = {Vector3D}; _Context = std::basic_format_context<std::__format::_Sink_iter<char>, char>; _Args = {std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::handle}]’
/usr/include/c++/13/format:3330:14: required from ‘auto std::make_format_args(_Args&& ...) [with _Context = basic_format_context<__format::_Sink_iter<char>, char>; _Args = {Vector3D&}]’
/usr/include/c++/13/format:3743:61: required from ‘std::string std::format(format_string<_Args ...>, _Args&& ...) [with _Args = {Vector3D}; string = __cxx11::basic_string<char>; format_string<_Args ...> = basic_format_string<char, Vector3D>]’
/main.cpp:5:16: required from here
cc1plus: note: set ‘-fconcepts-diagnostics-depth=’ to at least 2 for more detail
/usr/include/c++/13/format:2837:7: note: candidate: ‘std::basic_format_arg<_Context>::basic_format_arg() [with _Context = std::basic_format_context<std::__format::_Sink_iter<char>, char>]’
2837 | basic_format_arg() noexcept : _M_type(__format::_Arg_none) { }
| ^~~~~~~~~~~~~~~~
/usr/include/c++/13/format:2837:7: note: candidate expects 0 arguments, 1 provided
/usr/include/c++/13/format:2775:11: note: candidate: ‘constexpr std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::basic_format_arg(const std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >&)’
2775 | class basic_format_arg
| ^~~~~~~~~~~~~~~~
/usr/include/c++/13/format:2775:11: note: no known conversion for argument 1 from ‘Vector3D’ to ‘const std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >&’
/usr/include/c++/13/format:2775:11: note: candidate: ‘constexpr std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::basic_format_arg(std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >&&)’
/usr/include/c++/13/format:2775:11: note: no known conversion for argument 1 from ‘Vector3D’ to ‘std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >&&’
Однако когда я просто определяю функцию format
непосредственно в заголовочном файле vector.h
, все работает. Может кто-нибудь объяснить, что здесь происходит?
Проблема связана с типом возвращаемого значения auto
.
Когда вы делаете #include "base/vector.h"
, а затем вызываете std::format
, компилятор может видеть только объявление вашей функции format
:
template <>
struct std::formatter<Vector3D> : public std::formatter<std::string> {
auto format(const Vector3D &item, std::format_context &format_context) const;
};
но не реализация, как в другой единице перевода.
Компилятору необходимо будет проверить это объявление функции на соответствие некоторому ограничению. Однако, поскольку вы объявили функцию с типом возвращаемого значения auto
, она не может определить тип возвращаемого значения, просто просмотрев объявление.
Однако когда я просто определяю функцию форматирования непосредственно в заголовочном файле Vector.h, все работает.
Если вы это сделаете, компилятор сможет увидеть определение функции и определить тип возвращаемого значения.
Решение состоит в том, чтобы либо просто сохранить определение в заголовке, либо использовать decltype
для получения типа возвращаемого значения:
template <>
struct std::formatter<Vector3D> : public std::formatter<std::string> {
auto format(const Vector3D &item, std::format_context &format_context) const
-> decltype(format_context.out());
};
Выведенный тип возвращаемого значения разрешен только для функций с немедленно предоставленным определением. Тип возвращаемого значения является обязательной частью типа функции; поэтому он должен быть виден любому коду, использующему эту функцию.