Невозможно использовать std::apply для пользовательских типов

При реализации compressed_tupleclass для какого-то проекта, над которым я работаю, я столкнулся со следующей проблемой: я не могу передать экземпляры этого типа в std ::apply, хотя это должно быть возможно в соответствии с: https://en.cppreference.com / w / cpp / utility / apply.

Мне удалось довольно легко воспроизвести проблему, используя следующий фрагмент (Godbolt ):

      #include <tuple>

struct Foo {
public:
    explicit Foo(int a) : a{ a } {}

    auto &get_a() const { return a; }
    auto &get_a() { return a; }

private:
    int a;
};

namespace std {

template<>
struct tuple_size<Foo> {
    constexpr static auto value = 1;
};

template<>
struct tuple_element<0, Foo> {
    using type = int;
};

template<size_t I>
constexpr auto get(Foo &t) -> int & {
    return t.get_a();
}

template<size_t I>
constexpr auto get(const Foo &t) -> const int & {
    return t.get_a();
}

template<size_t I>
constexpr auto get(Foo &&t) -> int && {
    return std::move(t.get_a());
}

template<size_t I>
constexpr auto get(const Foo &&t) -> const int && {
    return move(t.get_a());
}

} // namespace std

auto foo = Foo{ 1 };
auto f = [](int) { return 2; };

auto result = std::apply(f, foo);

Когда я пытаюсь скомпилировать этот фрагмент кода, мне кажется, что он не может найти std::getперегрузки, которые я определил, даже если они должны идеально совпадать. Вместо этого он пытается сопоставить все другие перегрузки (std ::get(pair<T, U>), std::get(array<...>) и т. Д.), Не говоря уже о моих перегрузках. Я получаю постоянные ошибки во всех трех основных компиляторах (MSVC, Clang, GCC).

Итак, мой вопрос в том, является ли это ожидаемым поведением и просто невозможно использовать std::applyс пользовательскими типами? И есть ли обходной путь?

2 ответа

Решение

Итак, мой вопрос в том, является ли это ожидаемым поведением, и просто невозможно использовать std ::apply с пользовательскими типами?

Нет, нет.

В [tuple.apply] используется std:getвнутренне. Поскольку пользователям запрещено определять под namespace std, в настоящее время невозможно применить к пользовательским типам.

Также в реализациях libstdc++, libc ++ и MSVC-STL, std::get используется для внутреннего использования вместо неквалифицированного.

Вы можете спросить, в [tuple.creation] стандарт описывает tuple_cat следующее:

[Примечание 1. Реализация может поддерживать дополнительные типы в пакете параметров шаблона. Tuples которые поддерживают протокол -подобный, например pair а также array. - конец примечания]

Означает ли это, что другие служебные функции, например, должны поддерживать -подобные типы?

Обратите внимание, что, в частности, термин " tuple-like "не имеет конкретного определения на данный момент. Это было намеренно оставлено комитетом C++, чтобы заполнить этот пробел будущим предложением. Существует предложение, которое собирается начать улучшать этот вопрос, см. P2165R2 .


И есть ли обходной путь?

Текущий обходной путь - заново реализовать собственный std::apply и использовать неквалифицированный get внутренне.

На вопрос полностью ответил @康桓瑋. Я собираюсь опубликовать более подробную информацию о том, как обеспечить обходной путь.

Во-первых, вот общий C++20 applyфункция:

      #include<tuple>
#include<functional>

namespace my
{
    constexpr decltype(auto) apply(auto&& function, auto&& tuple)
    {
        return []<size_t ... I>(auto && function, auto && tuple, std::index_sequence<I...>)
        {
            using std::get;
            return std::invoke(std::forward<decltype(function)>(function)
                             , get<I>(std::forward<decltype(tuple)>(tuple)) ...);
        }(std::forward<decltype(function)>(function)
        , std::forward<decltype(tuple)>(tuple)
        , std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<decltype(tuple)> > >{});
    }
} //namespace my

Собственное пространство имен полезно для того, чтобы пользовательское применение не мешало стандартной версии. Безоговорочный призыв к getозначает (цитируя @Quuxplusone из его блога, который дает лучшее объяснение, с которым я когда-либо сталкивался):

Неквалифицированный вызов с использованием двухэтапного вызова, например my::xyzzy;xyzzy(t) указывает: «Я знаю один способ xyzzy, что бы это ни было, но сам T может знать способ получше. Если у Т есть мнение, ты должен доверять Т, а не мне».

Затем вы можете развернуть свой собственный класс, похожий на кортеж,

      struct my_tuple
{
    std::tuple<int,int> t;   
};

template<size_t I>
auto get(my_tuple t)
{
    return std::get<I>(t.t);
}

namespace std
{
    template<>
    struct tuple_size<my_tuple>
    {
        static constexpr size_t value = 2;
    };
}

При перегрузке get()и специализация std::tuple_size, функция применения затем работает должным образом. Более того, вы можете подключить любой совместимый стандартный тип:

      int main()
{
    auto test = [](auto ... x) { return 1; };

    my::apply(test, my_tuple{});
    my::apply(test, std::tuple<int,double>{});    
    my::apply(test, std::pair<int,double>{});    
    my::apply(test, std::array<std::string,10>{});    
}

ДЕМО

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