C ++で再帰的に整数のテンプレートパラメーターを一致させることは可能ですか?


8

次の問題があります。N次元ベクトルを次のように定義します

#include <vector>
#include <utility>
#include <string>


template <int N, typename T>
struct NVector{
    typedef std::vector<typename NVector<N-1,T>::type> type;
};
template <typename T> struct NVector<1,T> {
    typedef std::vector<T> type;
};

ネストされたベクトルのリーフ要素をいくら深くても変換でき、同じ形状の新しいネストされたベクトルを返す、より高次の関数マップを書きたいと思います。私が試してみました


template <int N, typename T, typename Mapper>
struct MapResult {
    typedef decltype ( (std::declval<Mapper>()) (std::declval<T>()) ) basic_type;
    typedef typename NVector<N, basic_type>::type vector_type;
};

template <int N, typename T, typename Mapper>
typename MapResult<N,T,Mapper>::vector_type 
    Map( typename NVector<N,T>::type const & vector,  Mapper mapper)
{
    typename MapResult<N,T,Mapper>::vector_type out;
    for(auto i = vector.begin(); i != vector.end(); i++){
        out.push_back(Map(*i,mapper));
    }
    return out;
}

template <typename T, typename Mapper>
typename MapResult<1,T,Mapper>::vector_type  
    Map(typename NVector<1,T>::type const & vector, Mapper mapper)
{
    typename MapResult<1,T,Mapper>::vector_type out;
    for(auto i = vector.begin(); i != vector.end(); i++){
        out.push_back(mapper(*i));
    }
    return out;
}

そしてそれをメインのように使用します

int main(){

    NVector<1,int>::type a = {1,2,3,4};
    NVector<2,int>::type b = {{1,2},{3,4}};

    NVector<1,std::string>::type as = Map(a,[](int x){return std::to_string(x);});
    NVector<2,std::string>::type bs = Map(b,[](int x){return std::to_string(x);});
}

しかし、コンパイルエラーが発生します

<source>:48:34: error: no matching function for call to 'Map'

    NVector<1,double>::type da = Map(a,[](int x)->int{return (double)x;});

                                 ^~~

<source>:20:5: note: candidate template ignored: couldn't infer template argument 'N'

    Map( typename NVector<N,T>::type const & vector,  Mapper mapper)

    ^

<source>:31:5: note: candidate template ignored: couldn't infer template argument 'T'

    Map(typename NVector<1,T>::type const & vector, Mapper mapper)

    ^

<source>:49:34: error: no matching function for call to 'Map'

    NVector<2,double>::type db = Map(b,[](int x)->int{return (double)x;});

                                 ^~~

<source>:20:5: note: candidate template ignored: couldn't infer template argument 'N'

    Map( typename NVector<N,T>::type const & vector,  Mapper mapper)

    ^

<source>:31:5: note: candidate template ignored: couldn't infer template argument 'T'

    Map(typename NVector<1,T>::type const & vector, Mapper mapper)

    ^

2 errors generated.

Compiler returned: 1

コンパイラは、控除によってパラメータNを計算するのに十分スマートではない(または標準では方法が指定されていない)と思います。これを達成する方法はありますか?

私は以前これを機能させていましたが、実際にはstd :: vectorから派生することによって別の方法で行われましたが、新しいラッパータイプを導入する必要なしに現在の既存のコードで機能させるとよいので、このソリューションは好きではありません。

/// define recursive case
template <int N, typename T>
struct NVector : std::vector<NVector<N-1,T>>;
/// define termination case
template <typename T> 
struct NVector<1, T> : public std::vector<T>;

https://godbolt.org/z/AMxpujのライブコード


私の頭の中から– T引数として控除可能なテンプレートタイプを使用し、使用する必要がありますenable_if
bartop

回答:


3

コンパイラーが型から引数の組み合わせへの逆マッピングを実行する方法がないため、typedef(特にヘルパークラス内で宣言されたtypedef)から推測することはできません。

(一般的なケースでは、誰かがを専門化している可能性があるためこれは不可能struct NVector<100, float> { using type = std::vector<char>; };であり、コンパイラーがこれが意図されているかどうかを知る方法がないと考えてください。)

コンパイラーを支援するために、逆マッピングを定義できます。

template<class T> struct NVT { static constexpr auto D = 0; using V = T; };
template<class T> struct NVT<std::vector<T>> : NVT<T> {
    static constexpr auto D = NVT<T>::D + 1;
};

可能な使用法(C ++ 17、しかし古風な方言に変換するのは十分簡単です):

template<class NV, class Mapper>
auto Map(NV const& vector, Mapper mapper) {
    static constexpr auto N = NVT<NV>::D;
    using T = typename NVT<NV>::V;
    if constexpr (N == 0)
        return mapper(vector);
    else
    {
        typename MapResult<N,T,Mapper>::vector_type out;
        for (auto const& x : vector)
            out.push_back(Map(x, mapper));
        return out;
    }
}

これは素晴らしいですね。C ++ 11に変換するための変換とは(if constexprを使用しない場合)。これを行う一般的な方法はありますか?古いコンパイラで立ち往生:(
bradgonesurfing

1
@bradgonesurfing N==0などの部分的な特殊化で構造体を作成
バートップ

先端をありがとう。動作します。godbolt.org/z/PdqcBu
bradgonesurfing

いいね。C ++ 11でこれを行った方法は次のとおりです。godbolt.org / z / bNzFk3-古いバージョンで書くためのスキルがどれだけ早く錆びているかは驚くべきことです。
ecatmur

それは私のバージョンよりもいいです。構造体を作成する必要はありませんでした。はい、constexprが非常に優れている場合。残念ですが、使えません。いくつかの調整を行うことで、コードをVS2010まで完全に機能させることができました。ゴールデン:) !!
ブラッドゴーニングサーフィン

2

他の回答ですでに指摘されているように、ここでの問題は、qualified-id入れ子になった名前指定が推論されていないコンテキスト[temp.deduct.type] /5.1であることです。他の回答でも、元のアプローチを機能させるためのさまざまな方法がすでに示されています。一歩下がって、あなたが実際に何をしたいのかを考えたいと思います。

すべての問題は、ヘルパーテンプレートの観点から作業しようとしているという事実から生じますNVector。このヘルパーテンプレートの唯一の目的は、nestedの特殊化を計算することですstd::vector。ヘルパーテンプレートの唯一の目的は、ネストされた入力ベクトル構造の各要素に任意の関数を適用した結果をキャプチャするために必要なMapResultネストの特殊化を計算することです。これらのヘルパーテンプレートの観点から関数テンプレートを表現する必要はありません。実際、私たちがそれらを取り除けば、人生はずっと単純になります。実際にやりたかったのは、入れ子構造の各要素に任意の関数を適用することだけです。だからそれをやってみましょう:std::vectormapperMapmapperstd::vector

template <typename T, typename Mapper>
auto Map(std::vector<T> const& vector, Mapper&& mapper) -> std::vector<decltype(mapper(std::declval<T>()))>
{
    std::vector<decltype(mapper(std::declval<T>()))> out;
    out.reserve(vector.size());
    for (auto& v : vector)
        out.push_back(mapper(v));
    return out;
}

template <typename T, typename Mapper>
auto Map(std::vector<std::vector<T>> const& vector, Mapper&& mapper) -> std::vector<decltype(Map(std::declval<std::vector<T>>(), mapper))>
{
    std::vector<decltype(Map(std::declval<std::vector<T>>(), mapper))> out;
    out.reserve(vector.size());
    for (auto& v : vector)
        out.push_back(Map(v, mapper));
    return out;
}

ここでの作業例

C ++ 14以降を使用できる場合は、末尾の戻り値の型を削除するだけです。


n D配列を保存して作業するだけの場合は、ネストされた構造がstd::vector必ずしも最も効率的な方法ではないことを考慮してください。各サブベクトルのサイズが異なる可能性がある場合を除いて、実行する動的メモリ割り当ての数を次元数とともに指数関数的に増やし、各要素へのポインタ追跡を行う理由はありません。1つだけ使用してstd::vector、n D配列のすべての要素を保持し、たとえば、この回答で提案されたのと同様の方法で、論理n D要素インデックスと1D線形ストレージインデックス間のマッピングを定義します。これを行うと、ベクターをネストするよりも効率的であるだけでなく、データが格納されているメモリレイアウトを簡単に変更することもできます。さらに、基礎となるストレージは単純な線形配列であるため、単純なループを使用してすべての要素を反復することができ、ある範囲の要素を別の要素にマッピングするという質問に対する答えは単純std::transformです…


申し訳ありませんが、私がやろうとしていることを逃しました。サポートする必要があるNレベルのネストに対してNマップ関数を作成したくないので、(N + 1)レベルのサポートが必要な場合は、別のネスト関数を作成する必要があります。stackoverflow.com/a/59965129/158285
bradgonesurfing

@bradgonesurfing私の理解では、の任意にネストされた構造にマッピング関数を適用したいということでしたstd::vectors。上記のアプローチはそれを正確に行い、どのNでも機能します!?2つのオーバーロードがあり、1つはさらに別のベクトルを含み、レベルの再帰につながるベクトルのケースに一致し、もう1つは再帰が停止する基本ケースを処理します…
Michael Kenzel

すみません、私の間違いです。正しく読みませんでした。ありがとう
bradgonesurfing

1
@bradgonesurfingこの例を拡張して、3ウェイのネストされたベクトルが機能することを示すテストケースを追加しました。godbolt.org / z / ksyn5k ;)
Michael

1
@bradgonesurfingわかりました。その場合は、ネストされたベクトルを使用したいと思います。念のため言っておきます。
マイケルケンゼル

1

およびNVectorを定義する必要はありません。MapResultMap

template <int N, typename T>
struct NVector{
    typedef std::vector<typename NVector<N-1,T>::type> type;
};
template <typename T> struct NVector<1,T> {
    typedef std::vector<T> type;
};

template <typename T, typename Mapper>
struct MapResult {
    typedef decltype(std::declval<Mapper>()(std::declval<T>())) type;
};

template <typename T, typename Mapper>
struct MapResult<std::vector<T>, Mapper> {
    typedef std::vector<typename MapResult<T, Mapper>::type> type;
};

template <typename T, typename Mapper, typename Result = typename MapResult<T, Mapper>::type>
Result Map(T const& elem, Mapper&& mapper)
{
    return mapper(elem);
}

template <typename T, typename Mapper, typename Result = typename MapResult<std::vector<T>, Mapper>::type>
Result Map(std::vector<T> const& vector, Mapper&& mapper)
{
    Result out;
    out.reserve(vector.size());
    for (auto& v : vector)
        out.push_back(Map(v, mapper));
    return out;
}

コードで発生したいくつかのエラーを修正しました。これでコンパイルされます。素晴らしい解決策。
ブラッドゴーニングサーフィン

残念ながら、VS2010コンパイラー(そうでなければなりません)は、関数のデフォルトのテンプレート引数をサポートしていません
bradgonesurfing

しかし、簡単に修正できます。デフォルトのテンプレートパラメータは、コピーペーストを防ぐための単なる砂糖でした。これはVS2010で機能します(必要な貧しい人々のために)gist.github.com/bradphelan/da494160adb32138b46aba4ed3fff967
bradgonesurfing

0

同じネストされたタイプを生成するテンプレートのインスタンスが多数存在する可能性があるためtypename NVector<N,T>::type、一般に、推定するN,Tことはできません。

1:1のマッピングを書いたことは知っていますが、言語はそれを必要としないので、このように逆方向に動作するためのサポートはありません。結局のところ、あなた書いたのですtypename NVector<N,T>::type、あなたが実際に渡しているのはstd::vector<std::vector<int>>何かです。それを取り消す一般的な方法はありません。

単純な解決策は、単なるベクターのtypedefを生成する方法ではなく、NVectorを値の型として使用することです。

template <int N, typename T>
struct NVector{
    using nested = std::vector<NVector<N-1,T>>;
    nested vec;
};
template <typename T> struct NVector<1,T> {
    using nested = std::vector<T>;
    nested vec;
};

次に、MapとMapResultを変更して、に関して直接機能するNVector<N,T>ようにします。これにより、通常どおり型の推論が可能になります。たとえば、一般的なマップは

template <int N, typename T, typename Mapper>
typename MapResult<N,T,Mapper>::vector_type 
    Map(NVector<N,T> const & vector,  Mapper mapper)
{
    typename MapResult<N,T,Mapper>::vector_type out;
    for(auto i = vector.vec.begin(); i != vector.vec.end(); i++){
        out.vec.push_back(Map(*i,mapper));
    }
    return out;
}

最後にNVector<1,int>、noの::type場合と同じようにローカル変数を宣言する必要があります。残念ながら、{}各レベルの周りにエクストラをラップする必要があるため、イニシャライザは少し醜くなります。NVectorただし、これを回避するためのコンストラクタをいつでも作成できます。

ああ、そしてstd::transformそのループを手動で書く代わりに使用することを検討してください。


OPのマッピングはT、タイプであることを禁止していないため、実際には1:1ではありませんstd::vector
n314159

0

部分的な特殊化を使用して、いわばNを逆に推論できます。

#include <iostream>
#include <vector>

template <typename T, int depth = 0>
struct get_NVector_depth {
    static constexpr int value = depth;
};

template <typename T, int depth>
struct get_NVector_depth<std::vector<T>, depth> {
    static constexpr int value = get_NVector_depth<T, depth+1>::value;
};

int main() {
    std::cout << get_NVector_depth<std::vector<std::vector<int>>>::value;
    std::cout << get_NVector_depth<std::vector<int>>::value;
}

これは、SFINAEで次のようなことを行うために使用できます。

template <typename T, typename Mapper, std::enable_if_t<get_NVector_depth<T>::value == 1, int> = 0>
typename MapResult<1,T,Mapper>::vector_type  
    Map(const T& vector, Mapper mapper)

0

あいまいであるため、コンパイラがあなたの意味を推測しようとしないのは完全に正しいことです。NVector<2, int>またはで関数を呼び出しますかNVector<1, std::vector<int>>?どちらも完全に有効であり、どちらも同じtypeメンバーtypedefを提供します。

おそらくこの型でベクトルを渡したため、以前のソリューションは機能しました(そのため、引数には型がNVector<2, int>あり、そこから適切なテンプレートパラメーターを簡単に推定できます)。私の考えでは、3つの可能性があります。

  1. std::vector再度カスタムタイプでラップします。しかし、私はそれを継承ではなく、メンバーとそのメンバーの型への暗黙の変換だけで行います。
  2. Nvector<N,T>呼び出しを明確にする何らかのタグパラメータを追加します(そうします)。
  3. 明示的なテンプレート引数を使用して呼び出します。

3番目が最も簡単で明確だと思います。


0

TおよびでN推定できません:

template <int N, typename T, typename Mapper>
typename MapResult<N,T,Mapper>::vector_type 
    Map(typename NVector<N,T>::type const & vector,  Mapper mapper)

代わりに、次のようにします。

// final inner transformation
template <typename T, typename Mapper>
auto Map(const std::vector<T>& v, Mapper mapper)
-> std::vector<typename std::decay<decltype(mapper(*std::begin(v)))>::type>
{
    std::vector<typename std::decay<decltype(mapper(*std::begin(v)))>::type> ret;
    ret.reserve(v.size());
    std::transform(std::begin(v), std::end(v), std::back_inserter(ret), mapper);
    return ret;
}

// recursive call
template <typename T, typename Mapper>
auto Map(const std::vector<std::vector<T>>& v, Mapper mapper)
-> std::vector<typename std::decay<decltype(Map(*std::begin(v), mapper))>::type>
{
    std::vector<typename std::decay<decltype(Map(*std::begin(v), mapper))>::type> ret;
    ret.reserve(v.size());
    std::transform(std::begin(v),
                   std::end(v),
                   std::back_inserter(ret),
                   [&](const std::vector<T>& inner){ return Map(inner, mapper);});
    return ret;
}

デモ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.