Разрешить шаблонные локальные классы

Сергей Садовников
Сергей Садовников

По состоянию на текущий момент стандарт позволяет объявлять полиморфные лямбда-функции (которые, согласно стандарту же) генерируются с шаблонным оператором вызова функции. Также стандарт разрешать использовать локальные классы в качестве параметров шаблонов. Также в стандарт включен std::variant<>. Но очень сложно собрать всё это вместе. Например, можно написать:

#include <iostream>
#include <variant>
#include <string>

using TestType = std::variant<int, std::string>;

int main()
{
    TestType v1{1};
    TestType v2{"Hello World!"};
    
    auto visitor = [](auto&& val) {std::cout << val << std::endl;};
    
    std::visit(visitor, v1);
    std::visit(visitor, v2);
}

Что выведет ожидаемое:

1
Hello World!

Но, при желании кастомизировать обработку для конкретного типа, нельзя написать:

#include <iostream>
#include <variant>
#include <string>

using TestType = std::variant<int, double, std::string>;

int main()
{
    TestType v1{1};
    TestType v2{0.123};
    TestType v3{"Hello World!"};
    
    auto visitor = [](auto&& val) {std::cout << val << std::endl;};
    struct Visitor
    {
        template<typename T>
        void operator()(T&& val) {std::cout << val << std::endl;}
        
        void operator()(const std::string& val) {std::cout << "String: " << val << std::endl;}
    };
    
    std::visit(Visitor(), v1);
    std::visit(Visitor(), v2);
    std::visit(Visitor(), v3);
}

Что на текущий момент по меньшей мере странно. Поэтому предлагается снять этот существующий де-юре, но не де-факто, запрет. 

13
рейтинг
2 комментария
Andrey Davydov
Ровно того, что Вы хотите, можно достичь следующим способом:

#include <iostream>
#include <variant>
#include <string>

using TestType = std::variant<int, double, std::string>;

template<typename... Functors>
auto overload(Functors &&... functors)
{
struct Result : Functors...
{
Result(Functors &&... functors)
: Functors(std::forward<Functors>(functors))...
{};
using Functors::operator()...;
};
return Result(std::forward<Functors>(functors)...);
};

int main()
{
TestType v1{1};
TestType v2{0.123};
TestType v3{"Hello World!"};

auto visitor = overload(
[] (auto && val) {std::cout << val << std::endl;},
[] (const std::string& val) {std::cout << "String: " << val << std::endl;}
);

std::visit(visitor, v1);
std::visit(visitor, v2);
std::visit(visitor, v3);
}
Proposal на overload уже существует (github.com/viboes/tags/blob/master/doc/proposals/overload/P0051R2.md). В какой еще ситуации может быть полезен локальный шаблонный класс?
Andrey Davydov
Сергей Садовников
Andrey Davydov, ИМХО, это больше похоже на костыль для обхода как раз описанного мною ограничения в языке - когда полиморфное поведение локального класса поиметь хочется, а нельзя. При некотором удобстве (обработчики можно задавать лямбдами) я тут вижу и ряд недостатков:
1. Лямбды не шарят между собой общий стейт, если только не описать этот стейт в скоупе функции. Но тогда инстанс такого overload'а может работать _исключительно_ в скоупе функции. В std::function его не запихнешь, в потоке не отстрелишь. То есть сделать стейтфул-функтор, не привязанный к контексту вызова, таким образом нельзя.
2. У каждой лямбды своя собственная копия списка захвата. В каких то случаях на это можно забить, а в других - выльется в перерасход памяти.
3. Нет явно заданного типа параметра шаблонного вызова. Для игр с if constexpr/enable_if (буде такие понадобятся) придётся прибегать к decltype + decay_t.
4. Возможно могут быть проблемы с поиском нужной перегрузки среди нескольких. Я с этим сталкивался при таком вот способе разворачивания иерархии. Но это надо проверить.

А применение таких классов (из очевидных) - привязанные к контексту вызова visitor'ы с доступом в приватную часть класса, в методе которого локальный класс-визитор определяется. Чтобы достичь такого же поведения от класса-визитора, определённого вне скоупа метода, надо заводить дружеские отношения, а значит - "светить" такие визиторы в определении класса, что не всегда приемлемо. По сути - полиморфные лямбды, вид в профиль, просто явно определяемые разработчиком.
Сергей Садовников
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).