Có thể khớp các tham số mẫu số nguyên đệ quy trong C ++ không?


8

Tôi có vấn đề sau đây. Tôi xác định một vectơ N chiều là như vậy

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

Tôi muốn viết một hàm bậc cao hơn Bản đồ có thể biến đổi các phần tử lá của vectơ lồng nhau cho dù sâu và trả về một vectơ lồng nhau mới có cùng hình dạng. Tôi đã thử


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

và sau đó sử dụng nó trong chính như

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

Tuy nhiên tôi nhận được lỗi biên dịch

<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

Tôi đoán rằng trình biên dịch không đủ thông minh (hoặc tiêu chuẩn không chỉ định cách thức) để tìm ra tham số N bằng cách khấu trừ. Có cách nào tôi có thể đạt được điều này?

Trước đây tôi đã làm việc này nhưng theo một cách khác bằng cách thực sự xuất phát từ std :: vector nhưng tôi không thích giải pháp này vì sẽ rất tuyệt nếu nó hoạt động với mã hiện tại mà không phải giới thiệu một loại trình bao bọc mới.

/// 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>;

mã trực tiếp tại https://godbolt.org/z/AMxpuj


Ra khỏi đầu tôi - bạn nên sử dụng loại mẫu khấu trừ Tlàm đối số và sử dụngenable_if
bartop

Câu trả lời:


3

Bạn không thể suy luận từ một typedef - đặc biệt là một typedef được khai báo trong một lớp của trình trợ giúp - bởi vì không có cách nào để trình biên dịch thực hiện ánh xạ ngược từ một kiểu sang kết hợp các đối số.

(Hãy xem xét rằng trong trường hợp chung, điều này là không thể vì ai đó có thể chuyên môn hóa struct NVector<100, float> { using type = std::vector<char>; };và trình biên dịch không có cách nào để biết liệu điều này có được dự định hay không.)

Để giúp trình biên dịch ra, bạn có thể xác định ánh xạ ngược:

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ó thể sử dụng (C ++ 17, nhưng đủ dễ để dịch sang phương ngữ cổ):

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

Điều này có vẻ tuyệt vời. Biến đổi là gì để biến nó thành C ++ 11 (không có nếu constexpr). Có một cách chung để làm điều này? Bị mắc kẹt với trình biên dịch cũ :(
bradgonesurfing

1
@bradgonesurfing Tạo struct với chuyên môn hóa một phần cho N==0và những người khác
bartop

Cảm ơn vì tiền hỗ trợ. Làm. godbolt.org/z/PdqcBu
bradgonesurfing

Đẹp. Đây là cách tôi đã làm trong C ++ 11: godbolt.org/z/bNzFk3 - thật tuyệt vời khi các kỹ năng viết trong các phiên bản cũ trở nên nhanh chóng!
ecatmur

Điều đó đẹp hơn phiên bản của tôi. Tôi không thực sự cần phải tạo ra các cấu trúc. Có nếu constexpr là siêu đẹp. Đáng tiếc tôi không thể sử dụng nó. Với một vài điều chỉnh, tôi đã có được mã hoạt động hoàn toàn trở lại VS2010. Vàng :) !!
bradgonesurfing

2

Như đã được chỉ ra trong các câu trả lời khác, vấn đề ở đây là bộ xác định tên lồng nhau trong id đủ điều kiện là bối cảnh không suy diễn [temp.deduct.type] /5.1 . Các câu trả lời khác cũng đã trình bày nhiều cách khác nhau để làm cho cách tiếp cận ban đầu của bạn hoạt động. Tôi muốn lùi lại một bước và xem xét những gì bạn thực sự muốn làm.

Tất cả các vấn đề của bạn xuất phát từ thực tế là bạn đang cố gắng làm việc theo mẫu của người trợ giúp NVector. Mục đích duy nhất của mẫu trợ giúp này dường như là để tính toán chuyên môn hóa lồng nhau std::vector. Mục đích duy nhất của mẫu trình trợ giúp MapResultdường như là để tính toán chuyên môn hóa lồng nhau std::vectorcần thiết để nắm bắt kết quả của việc áp dụng mapperhàm tùy ý của bạn cho từng thành phần của cấu trúc vectơ đầu vào lồng nhau. Không có gì buộc bạn phải thể hiện Mapmẫu chức năng của mình theo các mẫu của trình trợ giúp này. Trên thực tế, cuộc sống đơn giản hơn rất nhiều nếu chúng ta loại bỏ chúng. Tất cả những gì bạn thực sự muốn làm là áp dụng một mapperhàm tùy ý cho từng phần tử của std::vectorcấu trúc lồng nhau . Vì vậy, hãy làm điều đó:

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

ví dụ làm việc ở đây

Chỉ cần bỏ các kiểu trả về theo sau nếu bạn có thể sử dụng C ++ 14 hoặc mới hơn.


Nếu những gì bạn thực sự muốn làm chỉ là lưu trữ và làm việc trên một mảng n D, hãy xem xét rằng một cấu trúc lồng nhau std::vectorkhông nhất thiết là cách hiệu quả nhất để làm như vậy. Trừ khi bạn cần mỗi vectơ con có kích thước tiềm năng khác nhau, không có lý do gì để có số lượng phân bổ bộ nhớ động mà bạn thực hiện tăng theo cấp số nhân với số lượng kích thước và con trỏ đuổi theo từng yếu tố. Chỉ cần sử dụng một std::vectorđể giữ tất cả các phần tử của mảng n D và xác định ánh xạ giữa các chỉ số phần tử n D logic và chỉ số lưu trữ tuyến tính 1D, ví dụ, theo cách tương tự như những gì được đề xuất trong câu trả lời này. Làm như vậy sẽ không chỉ hiệu quả hơn các vectơ lồng nhau, mà còn cho phép bạn dễ dàng thay đổi bố cục bộ nhớ trong đó dữ liệu của bạn được lưu trữ. Hơn nữa, kể từ khi lưu trữ cơ bản là một mảng tuyến tính đơn giản, lặp lại tất cả các yếu tố trên có thể được thực hiện chỉ bằng một vòng lặp đơn giản và câu trả lời cho câu hỏi của bạn về ánh xạ một loạt các yếu tố khác chỉ đơn giản là sẽ std::transform...


Xin lỗi nhưng bạn đã bỏ lỡ những gì tôi đang cố gắng làm. Tôi không muốn viết các hàm N Map cho N cấp độ lồng nhau mà tôi cần hỗ trợ và sau đó phải viết một cấp độ khác khi tôi thấy rằng tôi cần (N + 1) mức hỗ trợ. Xem câu trả lời từ stackoverflow.com/a/59965129/158285
bradgonesurfing

@bradgonesurfing Hiểu biết của tôi là bạn muốn áp dụng hàm ánh xạ cho cấu trúc lồng nhau tùy ý std::vectors. Cách tiếp cận trên thực hiện chính xác điều đó và hoạt động cho bất kỳ N!? Có hai tình trạng quá tải, một quá trình khớp với trường hợp của một vectơ chứa một vectơ khác và dẫn đến đệ quy xuống một mức, và một xử lý trường hợp cơ sở trong đó phép đệ quy dừng lại
Michael Kenzel

xin lỗi, là lỗi của tôi. Tôi đã không đọc nó đúng cách. Thankyou
bradgonesurfing

1
@bradgonesurfing Tôi đã mở rộng ví dụ để bao gồm các trường hợp thử nghiệm cho vectơ lồng 3 chiều để chứng minh rằng nó hoạt động: godbolt.org/z/ksyn5k ;)
Michael Kenzel

1
@bradgonesurfing ok, tốt, trong trường hợp đó bạn sẽ muốn đi với các vectơ lồng nhau tôi đoán. Tôi chỉ nghĩ rằng tôi sẽ đề cập đến nó trong trường hợp.
Michael Kenzel

1

Bạn không cần NVectorphải xác định 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;
}

Tôi đã sửa một vài lỗi bạn có trong mã. Bây giờ nó biên dịch. Giải pháp tốt đẹp.
bradgonesurfing

Thật không may, trình biên dịch VS2010 (vâng tôi phải) không hỗ trợ các đối số mẫu mặc định trên các hàm
bradgonesurfing

Nhưng dễ dàng sửa chữa. Các tham số mẫu mặc định chỉ là đường để ngăn chặn việc sao chép. Điều này hoạt động trong VS2010 (cho bất kỳ linh hồn nghèo nào phải) gist.github.com/bradphelan/da494160adb32138b46aba4ed3fff967
bradgonesurfing

0

Nói chung typename NVector<N,T>::type, không cho phép bạn suy luận N,Tvì có thể có nhiều phiên bản của một mẫu tạo ra cùng một kiểu lồng nhau.

Tôi biết bạn đã viết ánh xạ 1: 1, nhưng ngôn ngữ không yêu cầu nó, và vì vậy không có hỗ trợ để làm việc ngược theo cách này. Rốt cuộc, bạn đã viết typename NVector<N,T>::type , nhưng những gì bạn thực sự vượt qua là std::vector<std::vector<int>>hoặc bất cứ điều gì. Không có cách nào chung để sao lưu nó.

Giải pháp đơn giản là sử dụng NVector như một loại giá trị thay vì chỉ là một cách để tạo ra các typedefs vector.

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

sau đó thay đổi Map và MapResult để hoạt động trực tiếp về mặt NVector<N,T>, cho phép loại trừ như bình thường. Ví dụ: Bản đồ chung trở thành

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

Cuối cùng, bạn cần khai báo các biến cục bộ của mình là NVector<1,int>không ::type, và thật không may, các công cụ khởi tạo trở nên xấu hơn một chút vì bạn cần phải bọc thêm {}xung quanh mỗi cấp. Bạn luôn có thể viết một hàm tạo để NVectorkhắc phục điều này.

Oh, và xem xét sử dụng std::transformthay vì viết vòng lặp đó bằng tay.


Ánh xạ của OP thực sự không phải là 1: 1 vì chúng không cấm Tthuộc loại std::vector.
n314159

0

Bạn có thể sử dụng một chuyên ngành một phần để suy ra N ngược để nói.

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

Điều này có thể được sử dụng với SFINAE để làm một cái gì đó như

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

Điều này hoàn toàn đúng, rằng trình biên dịch không cố đoán ý của bạn là gì, vì nó không rõ ràng. Bạn có muốn gọi hàm với NVector<2, int>hay NVector<1, std::vector<int>>không? Cả hai đều hoàn toàn hợp lệ và cả hai sẽ cung cấp cho bạn cùng một typetypedef.

Giải pháp trước đây của bạn đã hoạt động, vì có lẽ bạn đã vượt qua vectơ trong loại này (vì vậy đối số có loại NVector<2, int>và từ đó dễ dàng suy ra các tham số mẫu đúng). Bạn có ba khả năng theo ý kiến ​​của tôi:

  1. Bọc std::vectormột lần nữa trong loại tùy chỉnh của bạn. Nhưng tôi sẽ làm điều đó không phải với sự kế thừa mà chỉ với một thành viên và chuyển đổi ngầm định sang loại thành viên đó.
  2. Thêm một số loại tham số thẻ ( Nvector<N,T>sẽ làm) làm phân tán cuộc gọi.
  3. Gọi với các đối số mẫu rõ ràng.

Tôi nghĩ thứ ba là dễ nhất và rõ ràng nhất.


0

TNkhông được khấu trừ trong:

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

Thay vào đó, bạn có thể làm:

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

Bản giới thiệu

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.