簡體   English   中英

api 用於索引可變參數 arguments

[英]api for indexed variadic arguments

我不知道在將類似元組的對象解包到可調用處理程序中時,即在使用std::apply時,是否有一種好的和干凈的方法來索引可變參數 arguments 。

這是一個不完美但相當干凈的解決方案:

    const auto animals = std::make_tuple("cow", "dog", "sheep");

    // handwritten, stateful, bad...
    std::apply([](const auto& ... str){
        const auto print = [](const auto& str, size_t index){
            std::cout << index << ": " << str << '\n';
        };

        // this should not be done by the user!!!
        size_t i = 0;
        (print(str, i++), ...);
    }, animals);

此解決方案比使用std::index_sequence的重載更干凈,因為您不必在 lambda 的 scope 之外編寫任何代碼。塊 scope 內不允許使用模板,因此需要在外部創建一些幫助程序 class。
這很糟糕,因為有一個由用戶創建的可變 state。 應該沒有這樣的東西,索引應該是隱式按需可用的。

這是我認為需要的以及到目前為止我設法實現的:

    const auto animals = std::make_tuple("cow", "dog", "sheep");

    // desired
    // JavaScript-style destructuring. 
    // C++ structured bindings are not allowed as arguments
    // apply([](auto ... {value, index}){ ... }, animals);

    // still bad, but better - index is implicit and constant
    std::apply(indexed([](auto ... indexedValue){
        const auto print = [](const auto& indexedValue){
            const auto &[index, value] = indexedValue;
            std::cout << index << ": " << value << '\n';
        };
        (print(indexedValue), ...);
    }), animals);

C++ 不允許像 function arguments 這樣的結構化綁定,這是非常不幸的。 無論如何,我認為這個 api 比手動遞增計數器或編寫一些樣板幫助程序更好。
您所要做的就是 跟隨該死的火車 將您的可調用對象包裝到indexed() function 中。
而且它不需要對 STL 的部分進行任何修改。

但是,我的實施遠非最佳。 它產生的指令比第一個例子多得多:https://godbolt.org/z/3G4doao39

這是我對indexed() function 的實現,我想對其進行更正。

#include <cstddef>
#include <type_traits>
#include <tuple>

namespace detail {
    template <size_t I, typename T>
    struct _indexed
    {
        constexpr static size_t index = I;
        T value;

        constexpr _indexed(std::integral_constant<size_t, I>, T t)
            : value(t)
        {}

        template <size_t Elem>
        friend constexpr auto get(const detail::_indexed<I, T>& v) noexcept -> 
                std::tuple_element_t<Elem, detail::_indexed<I, T>>{
            if constexpr (Elem == 0)
                return I;
            if constexpr (Elem == 1)
                return v.value;
        }

    };

    template <size_t I, typename T>
    _indexed(std::integral_constant<size_t, I>, T) -> _indexed<I, T>;

    template <typename CRTP>
    class _add_indices
    {
    public:
        template <typename ... Args>
        constexpr decltype(auto) operator()(Args &&... args) const noexcept {
            return (*this)(std::make_index_sequence<sizeof...(Args)>(), std::forward<Args>(args)...);
        }
    private:
        template <typename ... Args, size_t ... I>
        constexpr decltype(auto) operator()(std::index_sequence<I...>, Args ... args) const noexcept {
            // does not compile 
            // return std::invoke(&CRTP::callable, static_cast<CRTP const&>(*this), 
            //     _indexed(std::integral_constant<size_t, I>{}, std::forward<Args>(args))...);
            return static_cast<const CRTP&>(*this).callable(_indexed(std::integral_constant<size_t, I>{}, std::forward<Args>(args))...);
        }
    };
}

template <size_t I, typename T>
struct std::tuple_size<detail::_indexed<I, T>> : std::integral_constant<size_t, 2> {};

template <size_t I, typename T>
struct std::tuple_element<0, detail::_indexed<I, T>>
{
    using type = size_t;
};

template <size_t I, typename T>
struct std::tuple_element<1, detail::_indexed<I, T>>
{
    using type = T;
};

template <typename Callable>
constexpr auto indexed(Callable c) noexcept{
    struct _c : detail::_add_indices<_c> {
        Callable callable;
    };

    return _c{.callable = c};
}

// api:
// apply(indexed([](auto ... indexedValue){}), tuple);

如果我正確理解你想要什么......在我看來你只需要一個類/結構如下

template <typename Callable>
struct indexed_call
{
  Callable c;

  template <std::size_t ... Is, typename ... As>
  constexpr auto call (std::index_sequence<Is...>, As && ... as) const {
    return c(std::pair{Is, std::forward<As>(as)}...);
  }

  template <typename ... As>
  constexpr auto operator() (As && ... as) const {
    return call(std::index_sequence_for<As...>{}, std::forward<As>(as)...);
  }
};

和一個明確的、瑣碎的、演繹指南

template <typename C>
indexed_call(C) -> indexed_call<C>;

並且調用稍微改變如下

std::apply(indexed_call{[](auto ... indexedValue){
   const auto print = [](const auto& iV){
      const auto &[index, value] = iV;
      std::cout << index << ": " << value << '\n';
   };
   (print(indexedValue), ...);
}}, animals);

或者,如果您願意,也可以簡化通話

std::apply(indexed_call{[](auto ... iV){
   ((std::cout << iV.first << ": " << iV.second << '\n'), ...);
}}, animals);

下面是一個完整的編譯示例

#include <type_traits>
#include <iostream>
#include <tuple>

template <typename Callable>
struct indexed_call
{
  Callable c;

  template <std::size_t ... Is, typename ... As>
  constexpr auto call (std::index_sequence<Is...>, As && ... as) const {
    return c(std::pair{Is, std::forward<As>(as)}...);
  }

  template <typename ... As>
  constexpr auto operator() (As && ... as) const {
    return call(std::index_sequence_for<As...>{}, std::forward<As>(as)...);
  }
};

template <typename C>
indexed_call(C) -> indexed_call<C>;

int main(){
    const auto animals = std::make_tuple("cow", "dog", "sheep");

    std::apply(indexed_call{[](auto ... indexedValue){
       const auto print = [](const auto& iV){
          const auto &[index, value] = iV;
          std::cout << index << ": " << value << '\n';
       };
       (print(indexedValue), ...);
    }}, animals);

    std::apply(indexed_call{[](auto ... iV){
       ((std::cout << iV.first << ": " << iV.second << '\n'), ...);
    }}, animals);
}

- - - 編輯 - - -

OP寫道

我不認為利用 std::pair 在語義上是正確的。 最好有.value, .index

我通常更喜歡使用標准組件,當功能等效時,但如果你想要帶有valueindex組件的東西,你可以添加一個簡單的結構(根據你的喜好命名)

template <typename V>
struct val_with_index
{
  std::size_t  index;
  V            value;
};

和另一個瑣碎的顯式演繹指南

template <typename V>
val_with_index(std::size_t, V) -> val_with_index<V>;

那么你必須修改call()方法來使用它而不是std::pair

template <std::size_t ... Is, typename ... As>
constexpr auto call (std::index_sequence<Is...>, As && ... as) const {
  return c(val_with_index{Is, std::forward<As>(as)}...);
} // ......^^^^^^^^^^^^^^

現在適用於雙 lambda 案例

對於簡化的情況,顯然你必須用indexvalue改變firstsecond

std::apply(indexed_call{[](auto ... iV){
   ((std::cout << iV.index << ": " << iV.value << '\n'), ...);
}}, animals); // ....^^^^^...............^^^^^

又是完整的編譯示例

#include <type_traits>
#include <iostream>
#include <tuple>

template <typename V>
struct val_with_index
{
  std::size_t  index;
  V            value;
};

template <typename V>
val_with_index(std::size_t, V) -> val_with_index<V>;

template <typename Callable>
struct indexed_call
{
  Callable c;

  template <std::size_t ... Is, typename ... As>
  constexpr auto call (std::index_sequence<Is...>, As && ... as) const {
    return c(val_with_index{Is, std::forward<As>(as)}...);
  }

  template <typename ... As>
  constexpr auto operator() (As && ... as) const {
    return call(std::index_sequence_for<As...>{}, std::forward<As>(as)...);
  }
};

template <typename C>
indexed_call(C) -> indexed_call<C>;

int main(){
    const auto animals = std::make_tuple("cow", "dog", "sheep");

    std::apply(indexed_call{[](auto ... indexedValue){
       const auto print = [](const auto& iV){
          const auto &[index, value] = iV;
          std::cout << index << ": " << value << '\n';
       };
       (print(indexedValue), ...);
    }}, animals);

    std::apply(indexed_call{[](auto ... iV){
       ((std::cout << iV.index << ": " << iV.value << '\n'), ...);
    }}, animals);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM