I was trying to specialize std::formatter<T>
for a basic custom type Vector3D
, but I’m receiving a compiler error. Here is a minimal reproducible example:
vector.h
defines the Vector3D
class and defines the std::formatter<Vector3D>
specialization, but only declares its format
function:
#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
includes vector.h
and defines 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);
}
Finally, main.cpp
attempts to std::format
a value of type Vector3D
:
#include "base/vectors.h"
int main()
{
std::format("{}n", Vector3D(1, 2, 3));
}
However, compiling this gives a huge compiler error:
/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> >&&’
When I just define the format
function directly in the header file vector.h
, though, everything works. Can someone explain what is going on here?