2012-02-15 4 views
9

Sự cố này dựa trên mã hoạt động với tôi trên GCC-4.6 nhưng không cho người dùng khác có CLang-3.0, cả ở chế độ C++ 0x.Xung đột giữa hàm tạo bản sao và hàm tạo chuyển tiếp

template <typename T> 
struct MyBase 
{ 
//protected: 
    T m; 

    template <typename Args...> 
    MyBase(Args&& ...x) : m(std::forward<Args>(x)...) {} 
}; 

Một đối tượng của MyBase có thể mất bất kỳ danh sách các đối số nhà xây dựng, miễn là T hỗ trợ chữ ký xây dựng. Vấn đề phải làm với các hàm thành viên đặc biệt.

  1. IIUC, mẫu nhà xây dựng hủy bỏ hàm tạo mặc định được tự động xác định. Tuy nhiên, vì mẫu có thể chấp nhận 0 đối số, mẫu sẽ hoạt động như một hàm tạo mặc định được xác định rõ ràng (miễn là T là cấu hình mặc định).
  2. IIUC, việc xác định chính sách sao chép xây dựng của lớp sẽ bỏ qua các mẫu của nhà xây dựng. Điều đó có nghĩa là trong trường hợp này, MyBase sẽ nhận được một hàm tạo bản sao được xác định tự động (miễn là T có thể sao chép được) sẽ kênh T sao chép-xây dựng.
  3. Áp dụng bước trước đó để di chuyển-xây dựng.

Vì vậy, nếu tôi chuyển một đối số constructor duy nhất, hàm tạo nào được gọi, chuyển tiếp hoặc sao chép ngầm?

typedef std::vector<Int> int_vector; 
typedef MyBase<int_vector> VB_type; 

int_vector a{ 1, 3, 5 }; 
VB_type  b{ a }; 
VB_type  c{ b }; // which constructor gets called 

Sự cố của người dùng của tôi đã sử dụng điều này làm lớp cơ sở. Trình biên dịch phàn nàn rằng lớp của anh ta không thể tổng hợp một hàm tạo bản sao được xác định tự động, bởi vì nó không thể tìm thấy một kết hợp với khuôn mẫu hàm tạo của lớp cơ sở. Không nên gọi số MyBase bản sao tự động-constructor cho công cụ tạo bản sao tự động của chính nó? CLang có lỗi khi đưa ra xung đột không?

+0

Câu hỏi thú vị. Tôi khuyên bạn nên thực hiện một sscce (xem http://sscce.org/) mà làm việc trong GCC và thất bại trong CLang để giúp chúng tôi hiểu vấn đề tốt hơn và tái tạo lại nó. Ngoài ra, vui lòng cung cấp cho chúng tôi thông báo lỗi chính xác từ CLang. –

+2

Thực ra, phiên bản gcc tương đối gần với đầu (tính đến 20120202) cũng không chấp nhận mã này. Có vẻ như nhà xây dựng chuyển tiếp đang được chọn ngay cả đối với bản sao. Tôi không hoàn toàn chắc chắn tại sao điều này. Nó không liên quan đến "cú pháp khởi tạo đồng bộ": ngay cả khi sử dụng dấu ngoặc đơn cho khai báo cuối cùng nó không biên dịch. –

+0

Mã của tôi là một cái gì đó tôi đã viết một thập kỷ trước cho Tăng cường và cập nhật cho C++ 11 tuần trước. Tôi không biết cách làm việc với tất cả hệ thống Trac của Boost, nhưng bạn có thể xem [những thay đổi của tôi] (https://svn.boost.org/trac/boost/changeset/76982) và [current state] (http) : //svn.boost.org/svn/boost/trunk/boost/utility/base_from_member.hpp) của tập tin (tại phiên bản 77031 tại văn bản này). – CTMacUser

Trả lời

9

Tôi chỉ ở trong quán bar với Richard Corden và giữa chúng tôi, chúng tôi kết luận rằng vấn đề không liên quan gì đến variadic hoặc rvalues. Cấu trúc sao chép được tạo ngầm được tạo ra trong trường hợp này lấy một đối số MyBase const&. Phương thức khởi tạo khuôn mẫu đã suy ra kiểu đối số là MyBase&. Đây là một trận đấu tốt hơn được gọi là mặc dù nó không phải là một nhà xây dựng bản sao.

Đoạn mã ví dụ tôi dùng để thử nghiệm là thế này:

#include <utility> 
#include <vector>i 

template <typename T> 
struct MyBase 
{ 
    template <typename... S> MyBase(S&&... args): 
     m(std::forward<S>(args)...) 
    { 
    } 
    T m; 
}; 

struct Derived: MyBase<std::vector<int> > 
{ 
}; 

int main() 
{ 
    std::vector<int>    vec(3, 1); 
    MyBase<std::vector<int> > const fv1{ vec }; 
    MyBase<std::vector<int> >  fv2{ fv1 }; 
    MyBase<std::vector<int> >  fv3{ fv2 }; // ERROR! 

    Derived d0; 
    Derived d1(d0); 
} 

tôi cần phải loại bỏ việc sử dụng danh sách khởi tạo bởi vì đây không được hỗ trợ bởi kêu vang, được nêu ra. Ví dụ này biên dịch ngoại trừ việc khởi tạo fv3 không thành công: trình tạo bản sao được tổng hợp cho MyBase<T> mất MyBase<T> const& và do đó chuyển fv2 gọi hàm tạo variadic chuyển tiếp đối tượng đến lớp cơ sở.

Tôi có thể đã hiểu sai câu hỏi nhưng dựa trên d0d1 có vẻ như cả hàm tạo mặc định và hàm tạo bản sao đều được tổng hợp. Tuy nhiên, điều này là với các phiên bản cập nhật của gcc và clang. Nghĩa là, nó không giải thích tại sao không có nhà xây dựng bản sao nào được tổng hợp bởi vì có một tổng hợp. Để nhấn mạnh rằng vấn đề này không liên quan gì đến danh sách đối số hoặc giá trị của đối số variadic: mã sau đây cho thấy vấn đề mà hàm dựng được gọi là mặc dù có vẻ như một hàm tạo bản sao được gọi và sao chép các constructor không bao giờ là mẫu.Đây thực sự là hành vi hơi ngạc nhiên mà tôi đã chắc chắn không biết:

#include <iostream> 
struct MyBase 
{ 
    MyBase() {} 
    template <typename T> MyBase(T&) { std::cout << "template\n"; } 
}; 

int main() 
{ 
    MyBase f0; 
    MyBase f1(const_cast<MyBase const&>(f0)); 
    MyBase f2(f0); 
} 

Kết quả là, thêm một constructor variadic như trong câu hỏi để một lớp mà không có bất kỳ nhà thầu khác những thay đổi nhà thầu bản sao hành vi hoạt động! Cá nhân, tôi nghĩ rằng điều này là khá đáng tiếc. Này có hiệu quả có nghĩa là lớp MyBase cần phải được tăng cường với sao chép và di chuyển nhà xây dựng cũng như:

MyBase(MyBase const&) = default; 
    MyBase(MyBase&) = default; 
    MyBase(MyBase&&) = default; 

Thật không may, điều này dường như không làm việc với gcc: nó than phiền về các nhà thầu bản sao defaulted (nó tuyên bố defaulted copy constructor lấy tham chiếu non-const không thể được định nghĩa trong định nghĩa lớp). Clang chấp nhận mã này mà không có bất kỳ khiếu nại nào. Sử dụng một định nghĩa của các nhà xây dựng bản sao lấy một tham chiếu không const làm việc với cả hai gcc và kêu vang:

template <typename T> MyBase<T>::MyBase(MyBase<T>&) = default; 
+0

@MooingDuck: Tôi không thể tạo lại sự cố khi sử dụng cơ sở tương ứng lớp (xem cập nhật của tôi cho câu trả lời). –

+0

Có, tôi đã sử dụng các tên khác nhau ban đầu và không cập nhật tất cả các địa điểm. Tôi hy vọng tôi đã sửa tất cả. –

1

Cá nhân tôi đã có vấn đề với bức ảnh chụp GCC trong một thời gian khá bây giờ. Tôi đã gặp khó khăn trong việc tìm ra những gì đang xảy ra (và nếu nó được cho phép) nhưng tôi đã đi đến một kết luận tương tự như Dietmar Kühl: các nhà xây dựng sao chép/di chuyển vẫn còn ở đây, nhưng không phải lúc nào cũng được ưu tiên độ phân giải.

Tôi đã sử dụng này để có được xung quanh vấn đề cho một số thời gian bây giờ là:

// I don't use std::decay on purpose but it shouldn't matter 
template<typename T, typename U> 
using is_related = std::is_same< 
    typename std::remove_cv<typename std::remove_reference<T>::type>::type 
    , typename std::remove_cv<typename std::remove_reference<U>::type>::type 
>; 

template<typename... T> 
struct enable_if_unrelated: std::enable_if<true> {}; 

template<typename T, typename U, typename... Us> 
struct enable_if_unrelated 
: std::enable_if<!is_related<T, U>::value> {}; 

Sử dụng nó với một constructor như của bạn sẽ trông như thế:

template< 
    typename... Args 
    , typename = typename enable_if_unrelated<MyBase, Args...>::type 
> 
MyBase(Args&&... args); 

Một số giải thích là theo thứ tự . is_related là chạy khỏi đặc điểm nhị phân của nhà máy để kiểm tra rằng hai loại giống hệt nhau bất kể các thông số cấp cao nhất (const, volatile, &, &&). Ý tưởng là các nhà xây dựng sẽ được bảo vệ bởi đặc điểm này là các nhà xây dựng 'chuyển đổi' và không được thiết kế để đối phó với các tham số của loại lớp, nhưng chỉ khi tham số đó ở vị trí đầu tiên. Công trình có thông số, ví dụ: (std::allocator_arg_t, MyBase) sẽ ổn thôi.

Bây giờ tôi đã từng có enable_if_unrelated là một siêu dữ liệu, nhưng vì nó rất thuận tiện để có các nhà xây dựng hoàn hảo chuyển tiếp variadic hoạt động trong trường hợp null nên tôi đã thiết kế lại nó để chấp nhận bất kỳ số đối số nào (mặc dù nó có thể được thiết kế để chấp nhận ít nhất một đối số, loại lớp của hàm tạo mà chúng tôi đang bảo vệ). Điều này có nghĩa là trong trường hợp của chúng ta nếu hàm tạo được gọi là không có đối số thì nó không phải là SFINAE. Nếu không, bạn cần thêm tuyên bố MyBase() = default;.

Cuối cùng, nếu hàm tạo chuyển tiếp đến cơ sở, một phương án khác là kế thừa hàm tạo của cơ sở đó thay vào đó (ví dụ: using Base::Base;). Đây không phải là trường hợp trong ví dụ của bạn.

0

Tôi đã upvoted câu trả lời của Dietmar vì tôi hoàn toàn đồng ý với anh ấy. Nhưng tôi muốn chia sẻ một "giải pháp" Tôi đã sử dụng một số thời gian trước đó để tránh những vấn đề này:

Tôi cố ý thêm vào một tham số giả để các nhà xây dựng variadic:

enum fwd_t {fwd}; 

template<class T> 
class wrapper 
{ 
    T m; 
public: 
    template<class...Args> 
    wrapper(fwd_t, Args&&...args) 
    : m(std::forward<Args>(args)...) 
    {} 
}; 

::: 

int main() 
{ 
    wrapper<std::string> w (fwd,"hello world"); 
} 

Đặc biệt kể từ khi các nhà xây dựng sẽ chấp nhận bất cứ điều gì mà không thông số giả này, có vẻ thích hợp để làm cho mã người dùng rõ ràng chọn đúng hàm tạo bởi (sắp xếp) "đặt tên" nó.

Có thể không thực hiện được trong trường hợp của bạn. Nhưng đôi khi bạn có thể thoát khỏi nó.