2017-04-03 16 views
11

Các mã sauC++ template variadic với đôi

#include <initializer_list> 
#include <vector> 

template<int ...> 
const std::vector<int>*make_from_ints(int args...) 
{ return new std::vector<int>(std::initializer_list<int>{args}); } 

được biên dịch (với GCC 6.3, trên Debian/Sid/x86-64) một cách chính xác, và tôi hy vọng nó cho một cuộc gọi như

auto vec = make_from_ints(1,2,3); 

để trả lại con trỏ về một số vectơ có số nguyên 1, 2, 3.

Tuy nhiên, nếu tôi thay thế int bằng double, đó là nếu tôi thêm thông tin sau (trong cùng một)tập tin ...) mã:

template<double ...> 
const std::vector<double>*make_from_doubles(double args...) 
{ return new std::vector<double>(std::initializer_list<double>{args}); } 

Tôi nhận được một lỗi biên dịch:

basiletemplates.cc:8:17: error: ‘double’ is not a valid type 
       for a template non-type parameter 
template<double ...> 
       ^~~ 

và tôi không hiểu tại sao. Sau khi cả hai số intdouble là các loại POD số vô hướng (được xác định trước trong tiêu chuẩn C++ 11).

Làm thế nào để có được một chức năng variadic mẫu để có thể mã:

auto dvec = make_from_doubles(-1.0, 2.0, 4.0); 

và có được một con trỏ đến một số vector của đôi chứa -1.0, 2.0, 4.0?

BTW, biên dịch cho C++ 14 (với g++ -Wall -std=c++14 -c basiletemplates.cc) và sử dụng clang++ (phiên bản 3.8.1) thay vì g++ không thay đổi bất kỳ điều gì.

+9

'int args ... 'được phân tách là' args int, ... ', định nghĩa của mẫu của bạn chỉ đơn giản là sai –

Trả lời

23
template<int ...> 
const std::vector<int>*make_from_ints(int args...) 
{ return new std::vector<int>(std::initializer_list<int>{args}); } 

Đoạn trên có vô số các vấn đề:

  • Trả về một const std::vector<int>* thay vì một std::vector<int> và không cần thiết sử dụng phân bổ động.

    • Thậm chí nếu bạn muốn sử dụng phân bổ động, bạn nên sử dụng std::make_unique thay vì new.
  • Bạn định nghĩa make_from_ints là hàm mẫu phải mất bất kỳ số lượng int mẫu thông số, nhưng bạn đã không cho những int là một tên - bạn không thể bao giờ sử dụng chúng!

  • Chữ ký của bạn thực sự được phân tích cú pháp là make_from_ints(int args, ...) - đây là chữ ký C va_args không có gì liên quan đến mẫu variadic.

    • Cú pháp chính xác cho gói đối số là type... name.

Nếu bạn muốn chấp nhận bất kỳ số lượng các đối số của một loại hình cụ thể mà làm việc độc đáo với mẫu đối số trích, cách dễ nhất là sử dụng thường xuyên variadic mẫu chấp nhận một số lượng tùy ý các loại và static_assert s loại của chúng (hoặc sử dụng std::enable_if cho SFINAE thân thiện). Dưới đây là một ví dụ:

template <typename... Ts> 
auto make_from_ints(Ts... xs) 
{ 
    static_assert((std::is_same<Ts, int>::value && ...)); 
    return std::vector<int>{xs...}; 
} 

template <typename... Ts> 
auto make_from_doubles(Ts... xs) 
{ 
    static_assert((std::is_same<Ts, double>::value && ...)); 
    return std::vector<double>{xs...}; 
} 

Cách sử dụng:

for(auto x : make_from_ints(1,2,3,4)) std::cout << x << " "; 
std::cout << "\n"; 
for(auto x : make_from_doubles(1.0,1.5,2.0,2.5)) std::cout << x << " "; 

1 2 3 4

1 1.5 2 2.5

live example on wandbox


Lưu ý rằng tôi đang sử dụng một C++17 fold expression để kiểm tra xem tất cả Ts... là của một loại hình cụ thể ở đây :

static_assert((std::is_same<Ts, int>::value && ...)); 

Nếu bạn không có quyền truy cập vào C++ 17 tính năng, điều này có thể dễ dàng thay thế bằng một cái gì đó như:

template <typename... Ts> 
constexpr auto all_true(Ts... xs) 
{ 
    for(auto x : std::initializer_list<bool>{xs...}) 
     if(!x) return false; 

    return true; 
} 

// ... 

static_assert(all_true(std::is_same<Ts, int>{}...)); 
+0

Có phải' && ... 'thực sự theo nghĩa đen? Nó có nghĩa là gì? –

+1

@BasileStarynkevitch: xin lỗi vì không giải thích điều đó - đó là biểu thức gấp nếp C++ 17. Sẽ cải thiện câu trả lời của tôi. –

+2

Thay vì biểu thức nếp gấp, bạn có thể sử dụng 'std :: combination', mặc dù thừa nhận' std :: combination' là một tính năng C++ 17. Tôi tìm thấy nó dễ dàng hơn để đọc hơn biểu thức gấp mặc dù: 'std :: kết hợp ...> :: giá trị' – Justin