Почему разделение интерфейса и реализация специализации `std::formatter` приводят к сбою ограничений?

Я пытался специализироваться 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, все работает. Может кто-нибудь объяснить, что здесь происходит?

Выведенный тип возвращаемого значения разрешен только для функций с немедленно предоставленным определением. Тип возвращаемого значения является обязательной частью типа функции; поэтому он должен быть виден любому коду, использующему эту функцию.

Red.Wave 14.07.2024 07:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
71
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема связана с типом возвращаемого значения 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());
};

Другие вопросы по теме