C ++テンプレートテンプレート引数タイプの控除


10

文字列のコンテナを調べて、パターンの一致を見つけて出力するコードがあります。テンプレート化された関数fooで印刷が実行されます

コード

#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <string>
#include <tuple>
#include <utility>

template<typename Iterator, template<typename> class Container>
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)
{
    for (auto const &finding : findings)
    {
        std::cout << "pos = " << std::distance(first, finding.first) << " ";
        std::copy(finding.first, finding.second, std::ostream_iterator<char>(std::cout));
        std::cout << '\n';
    }
}

int main()
{
    std::vector<std::string> strs = { "hello, world", "world my world", "world, it is me" };
    std::string const pattern = "world";
    for (auto const &str : strs)
    {
        std::vector<std::pair<std::string::const_iterator, std::string::const_iterator>> findings;
        for (std::string::const_iterator match_start = str.cbegin(), match_end;
             match_start != str.cend();
             match_start = match_end)
        {
            match_start = std::search(match_start, str.cend(), pattern.cbegin(), pattern.cend());
            if (match_start != match_end)
                findings.push_back({match_start, match_start + pattern.size()});
        }
        foo(str.cbegin(), findings);
    }

    return 0;
}

コンパイルすると、提供されているイテレータの不整合が原因で型の演繹が失敗したというエラーが発生します。その型は多様であることがわかります。

GCCコンパイルエラー:

prog.cpp:35:9: error: no matching function for call to 'foo'
        foo(str.cbegin(), findings);
        ^~~
prog.cpp:10:6: note: candidate template ignored: substitution failure [with Iterator = __gnu_cxx::__normal_iterator<const char *, std::__cxx11::basic_string<char> >]: template template argument has different template parameters than its corresponding template template parameter
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)
     ^
1 error generated.

Clangの出力:

main.cpp:34:9: error: no matching function for call to 'foo'
        foo(str.cbegin(), findings);
        ^~~
main.cpp:9:6: note: candidate template ignored: substitution failure [with Iterator = std::__1::__wrap_iter<const char *>]: template template argument has different template parameters than its corresponding template template parameter
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)

何をキャッチしていませんか?テンプレートテンプレートタイプの控除の使用が間違っていて、標準の観点からは乱用のように見えますか?いずれもG ++ - 9.2listdc ++ 11打ち鳴らす++のlibc ++はこれをコンパイルすることができます。


1
フラグ-std=c++17付きのGCC と-std=c++17-frelaxed-template-template-argsフラグ付きのClangで動作します。それ以外の場合は、アロケータ用に別のテンプレートパラメータが必要なようです。
HolyBlackCat

@HolyBlackCat、本当にありがとう
dannftk

回答:


10

C ++ 17以降、コードは正常に動作するはずです。(gcc10でコンパイルされます。)

テンプレートテンプレート引数にstd::vectorは2つのテンプレートパラメータがあります(2つ目のパラメータにはデフォルト引数がありますstd::allocator<T>)が、テンプレートテンプレートパラメータにContainerは1つしかありません。C ++ 17(CWG 150)以降、デフォルトのテンプレート引数は、テンプレートテンプレート引数がより少ないテンプレートパラメーターでテンプレートテンプレートパラメーターと一致するように許可されています。

template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };

template<template<class> class P> class X { /* ... */ };

X<A> xa; // OK
X<B> xb; // OK in C++17 after CWG 150
         // Error earlier: not an exact match

C ++ 17には、事前にテンプレートテンプレートパラメータのデフォルト引数と第二テンプレートパラメータを定義することができContainer、例えば

template<typename Iterator, template<typename T, typename Alloc=std::allocator<T>> class Container>
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)

または、パラメータパックを適用します

template<typename Iterator, template<typename...> class Container>
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)

1

C ++の一部のバージョンでは、Container一致させることはできませんstd::vectorので、std::vector実際にはありませんtemplate <typename> class。これtemplate <typename, typename> classは、2番目のパラメーター(アロケータータイプ)にデフォルトのテンプレート引数がある場所です。

別のテンプレートパラメータを追加しtypename Allocて関数パラメータを作成することContainer<std::pair<Iterator, Iterator>, Alloc>もできますが、それは他のコンテナタイプでは問題になる可能性があります。

ただし、関数は実際にはテンプレートテンプレートパラメーターを使用しないため、テンプレートContainerテンプレート引数を推測する際のすべての問題点と制限事項があるため、このような複雑なテンプレート引数を推測する必要はありません。

template<typename Iterator, class Container>
void foo(Iterator first, Container const &findings);

これもIterator、3つの異なる場所でまったく同じ型として推定する必要はありません。つまり、X::iteratoras firstと、それを含むコンテナ、X::const_iteratorまたはその逆を渡すことが有効であり、テンプレート引数の推定は引き続き成功する可能性があります。

わずかな欠点の1つは、別のテンプレートがSFINAEテクニックを使用しての署名fooが有効かどうかを判断しようとすると、その宣言はなどのほぼすべてに一致することですfoo(1.0, 2)。多くの場合、これは特定の目的の機能にとって重要ではありませんが、少なくとも汎用の機能については、より制限的(または「SFINAEフレンドリー」)であると便利です。次のような基本的な制限を追加できます。

// Require Container is container-like (including raw array or std::initializer_list)
// and its values have members first and second of the same type,
// which can be compared for equality with Iterator.
template <typename Iterator, class Container>
auto foo(Iterator first, Container const &findings)
    -> std::void_t<decltype(first == std::begin(findings)->first),
           std::enable_if_t<std::is_same_v<std::begin(findings)->first, 
                            std::begin(findings)->second>>>;

実際、私は常に、パラメーターで提供されるコンテナーが最初のパラメーターのタイプを持つイテレーターのstd :: pairとして値を伝達することを確認したいので、提供したテンプレート関数の最初の単純化は私の要件を満たしていないようですこれまでの2つ目は、SFINAEを使用したソリューションで実現します。とにかく、ありがとうございました
dannftk
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.