Pengurangan tipe argumen template templat C ++

10

Saya memiliki kode yang menemukan dan mencetak kecocokan suatu pola ketika melewati wadah string. Pencetakan dilakukan dalam fungsi foo yang templated

Kode

#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;
}

Ketika mengkompilasi saya punya kesalahan bahwa deduksi tipe telah gagal karena inkonsistensi iterator yang disediakan, tipenya ternyata beragam.

Kesalahan kompilasi 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.

Output dentang :

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)

Apa yang tidak saya tangkap? Apakah pemanfaatan jenis template template saya salah dan muncul penyalahgunaan dari sudut pandang standar? Baik g ++ - 9.2 dengan listdc ++ 11 atau clang ++ dengan libc ++ dapat mengkompilasi ini.

dannftk
sumber
1
Ini bekerja pada GCC dengan -std=c++17dan pada Dentang dengan -std=c++17-frelaxed-template-template-argsbendera. Kalau tidak , Anda perlu parameter templat lain untuk pengalokasi.
HolyBlackCat
@HolyBlackCat, memang, terima kasih
dannftk

Jawaban:

10

Kode Anda harus berfungsi dengan baik sejak C ++ 17. (Ini dikompilasi dengan gcc10 .)

Argumen template template std::vectormemiliki dua parameter template (yang ke-2 memiliki argumen default std::allocator<T>), tetapi parameter template template Containerhanya memiliki satu. Karena C ++ 17 ( CWG 150 ), argumen template default diizinkan untuk argumen template template untuk mencocokkan parameter template template dengan lebih sedikit parameter template.

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

Sebelum C ++ 17, Anda dapat menentukan parameter template 2 dengan argumen default untuk parameter template template Container, misalnya

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

Atau terapkan paket parameter .

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

Di beberapa versi C ++, Containertidak bisa cocok std::vector, karena std::vectorsebenarnya bukan a template <typename> class. Itu adalah template <typename, typename> classtempat parameter kedua (tipe pengalokasi) memiliki argumen templat default.

Meskipun bisa berfungsi untuk menambahkan parameter templat lain typename Allocmembuat parameter fungsi Container<std::pair<Iterator, Iterator>, Alloc>, itu bisa menjadi masalah untuk jenis wadah lainnya.

Tetapi karena fungsi Anda tidak benar-benar menggunakan parameter templat templat Container, Anda tidak perlu memerlukan deduksi argumen templat yang rumit, dengan semua gotcha dan batasan menyimpulkan argumen templat templat:

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

Ini juga tidak perlu Iteratordisimpulkan sebagai tipe yang sama persis di tiga tempat berbeda. Berarti itu akan berlaku untuk lulus X::iteratorsebagai firstdan wadah berisi X::const_iteratoratau sebaliknya, dan pengurangan argumen templat masih bisa berhasil.

Satu kelemahannya adalah jika templat lain menggunakan teknik SFINAE untuk mencoba menentukan apakah tanda tangan foovalid, deklarasi itu akan cocok dengan hampir semua hal, seperti foo(1.0, 2). Ini sering tidak penting untuk fungsi tujuan tertentu, tapi senang menjadi lebih membatasi (atau "ramah SFINAE") setidaknya untuk fungsi tujuan umum. Kita dapat menambahkan batasan dasar dengan sesuatu seperti:

// 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>>>;
aschepler
sumber
Sebenarnya saya selalu ingin memastikan bahwa wadah yang disediakan dalam parameter menyampaikan nilai sebagai std :: pair iterators yang memiliki tipe parameter pertama, oleh karena itu penyederhanaan pertama dari fungsi template yang Anda tawarkan tidak dapat memenuhi persyaratan saya, sebaliknya untuk ini yang kedua solusi Anda dengan SFINAE akan dilakukan. Bagaimanapun, terima kasih banyak
dannftk