4 votes

Bibliothèque C++ {fmt}, types définis par l'utilisateur avec champs de remplacement imbriqués ?

J'essaie d'ajouter {fmt} à mon projet, et tout se passe bien, sauf que j'ai rencontré un petit problème en essayant d'ajouter un type défini par l'utilisateur pour ma simple classe Vec2.

struct Vec2 { float x; float y; };

Ce que j'aimerais, c'est pouvoir utiliser les mêmes drapeaux/arguments de format que le type float intégré de base, mais en les dupliquant à la fois aux membres x et y du vec2, avec des parenthèses autour.

Par exemple, avec un simple flotteur :

fmt::format("Hello '{0:<8.4f}' World!", 1.234567);
// results in "Hello '1.2346  ' World!"

Avec ma classe vec2 :

Vec2 v {1.2345678, 2.3456789};
fmt::format("Hello '{0:<8}' World!", v);
// results in "Hello (1.2346  , 2.3457  )' World!"

Mais ma méthode naïve consistant à copier le contenu du champ de remplacement ne fonctionne pas lorsque nous essayons d'utiliser des champs de remplacement imbriqués. Par exemple avec un float :

fmt::format("Hello '{0:<{1}.4f}' World!", 1.234567, 8);
// results in "Hello '1.2346  ' World!"

Mais en essayant avec mon type Vec2...

Vec2 v {1.2345678, 2.3456789};
fmt::format("Hello '{0:<{1}.4f}' World!", v, 8);
// throws format_error, what(): "argument not found"

Bien sûr, cela se produit parce que tout ce que je fais, c'est copier le champ de remplacement après le ':' et avant le '}', et respecter l'équilibre des {}, donc si j'ai utilisé des champs de remplacement imbriqués, il y aura un {} à l'intérieur qui fera référence à un argument de la liste originale, ce qui n'est pas bon pour cela.

Ma spécialisation pour le type défini par l'utilisateur :

struct Vec2
{
    float x;
    float y;
};

template<>
struct fmt::formatter<Vec2>
{
    auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin())
    {
        int curlyBalance = 1;
        auto it = ctx.begin(), end = ctx.end();
        while (it != end)
        {
            if (*it == '}')
            {
                --curlyBalance;
            }
            else if (*it == '{')
            {
                ++curlyBalance;
            }

            if (curlyBalance <= 0)
                break;
            else
                ++it;
        }

        const char* beginPtr = &(*ctx.begin());
        const char* endPtr = &(*it);
        size_t len = endPtr - beginPtr;

        if (len == 0)
        {
            formatStr = "{}";
        }
        else
        {
            formatStr = "{0:";
            formatStr += std::string(beginPtr, len + 1);
        }

        return it;
    }

    template <typename FormatContext>
    auto format(const Vec2& vec, FormatContext& context)
    {
        fmt::format_to(context.out(), "(");
        fmt::format_to(context.out(), formatStr, vec.x);
        fmt::format_to(context.out(), ", ");
        fmt::format_to(context.out(), formatStr, vec.y);
        return fmt::format_to(context.out(), ")");
    }

    std::string formatStr;
};

int main()
{
    std::cout << "Hello world!" << std::endl;
    Vec2 v {1.234567, 2.345678};

    // Simple, static width.
    //std::string fmtResult = fmt::format("Hello '{0:<8.4f}' World!\n", v, 5);

    // Dynamic width, oh god, oh dear god no!
    std::string fmtResult = fmt::format("Hello '{0:<{1}}' World!\n", v, 5);

    std::cout << fmtResult;
}

Il semble que ce qui doit se passer, c'est que ma fonction d'analyse doit avoir accès aux autres args afin de pouvoir remplir le champ de remplacement imbriqué avec la valeur correcte... mais je suis encore novice dans cette bibliothèque, et j'apprécierais beaucoup d'avoir de l'aide à ce sujet !

Lien vers Godbolt : https://godbolt.org/z/6fxWszTT8

4voto

vitaut Points 10255

Vous pouvez réutiliser formatter<float> pour cela ( https://godbolt.org/z/vb9c5ffd5 ) :

template<> struct fmt::formatter<Vec2> : formatter<float> {
  template <typename FormatContext>
  auto format(const Vec2& vec, FormatContext& ctx) {
    auto out = ctx.out();
    *out = '(';
    ctx.advance_to(out);
    out = formatter<float>::format(vec.x, ctx);
    out = fmt::format_to(out, ", ");
    ctx.advance_to(out);
    out = formatter<float>::format(vec.y, ctx);
    *out = ')';
    return out;
  }
};

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X