Định dạng mẫu cho tên phụ thuộc


8

Tôi đã gặp vấn đề sau bằng cách biên dịch ví dụ sau:

template <int N>
class Matrix {
public:
    template <int Idx>
    int head() {
        return Idx;
    }
};

template <typename T>
class Test {
    static constexpr int RayDim = 3;
public:
    int func() const {
        Matrix<RayDim> yF;
        return yF.head<1>();
        //        ^ is template keyword required here?
    }
};

struct Empty {};

void test() {
    Test<Empty> t;
}

Liên kết đến Trình biên dịch Explorer: https://godbolt.org/z/js4XaP

Mã biên dịch với GCC 9.2 và MSVC 19,22 nhưng không có clang 9.0.0. Clang nói rằng một từ khóa mẫu là bắt buộc. Nếu static constexpr int RayDim = 3;được chuyển vào int func() constclang chấp nhận nó.

Như đã nêu như một nhận xét trong khối mã, từ khóa mẫu có cần thiết cho yF.head<1>()không?


Tôi nghĩ rằng bạn có thể đang gặp phải một vấn đề triển khai cụ thể về tiếng kêu trong đó việc khớp các kiểu mẫu với các tham số chưa được triển khai đầy đủ: clang.llvm.org/cxx_status.html#p0522
Eddy Luten 13/11/19

Tôi nghĩ rằng Matrix<RayDim>không phải là một loại phụ thuộc và vì vậy từ khóa là không cần thiết. Tôi có thể có thời gian cho một câu trả lời sau.
aschepler

@EddyLuten P0522 là về các tham số mẫu mẫu và không có bất kỳ thông tin nào trong ví dụ này.
aschepler

Một ví dụ đơn giản có vẻ liên quan: godbolt.org/z/KpXpYs
Evg

Câu trả lời:


1

Các templatetừ khóa không nên được yêu cầu ở đây, vì vậy kêu vang là không chính xác để từ chối chương trình.

Tất cả số phần và đoạn trích dẫn của C ++ Standard và các trích dẫn bên dưới đều giống nhau cho bản nháp C ++ 17 N4659 và bản nháp C ++ 20 được liên kết hiện tại.

Yêu cầu templatesau một .hoặc ->hoặc ::mã thông báo khi đặt tên mẫu thành viên là trong [temp.names] / 4 . Đoạn đầu tiên liệt kê các trường hợp từ khóa không được phép, sau đó là trường hợp tùy chọn và không tạo ra sự khác biệt, sau đó:

Trong tất cả các bối cảnh khác, khi đặt tên một chuyên môn mẫu của một thành viên của một chuyên ngành không xác định ( [temp.dep.type] ), tên mẫu thành viên sẽ được thêm tiền tố bởi từ khóa template.

"Thành viên của một chuyên ngành không xác định" là thành viên của loại phụ thuộc không phải là "khởi tạo hiện tại". Vì vậy, câu hỏi là liệu Matrix<RayDim>một loại phụ thuộc. Vì vậy, chúng tôi xem xét [temp.dep.type] / 9:

Một loại là phụ thuộc nếu nó là

  • một tham số mẫu,
  • một thành viên của một chuyên ngành chưa biết,
  • một lớp lồng nhau hoặc liệt kê là một thành viên phụ thuộc của khởi tạo hiện tại,
  • một loại đủ điều kiện cv trong đó loại không đủ tiêu chuẩn cv phụ thuộc,
  • một loại hợp chất được xây dựng từ bất kỳ loại phụ thuộc,
  • một kiểu mảng có kiểu phần tử phụ thuộc hoặc có ràng buộc (nếu có) phụ thuộc vào giá trị,
  • một loại hàm có đặc tả ngoại lệ phụ thuộc vào giá trị,
  • một đơn giản-template-id trong đó một trong hai tên mẫu là một tham số mẫu hoặc bất kỳ đối số mẫu là một loại phụ thuộc hoặc một biểu thức đó là loại phụ thuộc hoặc giá trị phụ thuộc hoặc là một mở rộng gói, hoặc
  • biểu thị bằng decltype(biểu thức) , trong đó biểu thức phụ thuộc vào loại.

Matrix<RayDim>rõ ràng không phải là một tham số mẫu, bất kỳ loại thành viên nào, đủ điều kiện cv, loại mảng, loại chức năng hoặc được chỉ định bởi decltype. Nó là một loại hỗn hợp, nhưng nó chỉ sử dụng một tên mẫu và một biểu thức, do đó không được xây dựng từ bất kỳ loại nào khác.

Điều đó để lại trường hợp id-template-id đơn giản . Tên mẫu Matrixkhông phải là một tham số mẫu. Đối số mẫu RayDimlà một biểu thức, vì vậy bây giờ để xem liệu nó phụ thuộc vào loại hay phụ thuộc vào giá trị.

"Phụ thuộc loại" được định nghĩa trong [temp.dep.expr] . Chỉ đoạn 3 có thể áp dụng cho một định danh đơn độc như RayDim:

Một biểu thức id phụ thuộc vào kiểu nếu nó chứa

  • một định danh được liên kết bởi tra cứu tên với một hoặc nhiều khai báo được khai báo với loại phụ thuộc,
  • một mã định danh được liên kết bởi tra cứu tên với tham số mẫu không phải loại được khai báo với loại có chứa loại giữ chỗ,
  • một mã định danh được liên kết bởi tra cứu tên với một biến được khai báo với một loại có chứa loại giữ chỗ ([dcl.spec.auto]) trong đó trình khởi tạo phụ thuộc vào loại,
  • một mã định danh được liên kết bởi tra cứu tên với một hoặc nhiều khai báo các hàm thành viên của khởi tạo hiện tại được khai báo với loại trả về có chứa loại giữ chỗ,
  • một mã định danh được liên kết bởi tra cứu tên với một khai báo ràng buộc có cấu trúc có dấu khởi tạo hoặc dấu bằng bằng phụ thuộc kiểu,
  • mã định danh __func__([dcl.fct.def.general]), trong đó bất kỳ hàm kèm theo nào là một mẫu, một thành viên của mẫu lớp hoặc lambda chung,
  • một id mẫu phụ thuộc,
  • một chuyển đổi chức năng-id mà xác định một loại phụ thuộc, hoặc
  • một công cụ xác định tên lồng nhau hoặc một id đủ điều kiện đặt tên cho một thành viên của một chuyên ngành không xác định;

hoặc nếu nó đặt tên một thành viên phụ thuộc của khởi tạo hiện tại là thành viên dữ liệu tĩnh thuộc loại "mảng không xác định ràng buộc T" đối với một số T([temp.static]).

RayDimchắc chắn không chứa bất kỳ __func__, id mẫu , chuyển đổi-chức năng-id , tên cụ thể lồng nhau , hoặc id-id . Tra cứu tên tìm thấy khai báo thành viên tĩnh của mẫu lớp. Khai báo đó RayDimchắc chắn không phải là tham số mẫu , hàm thành viên hoặc khai báo ràng buộc có cấu trúc và kiểu của nó const intchắc chắn không phải là kiểu phụ thuộc hoặc kiểu mảng và không chứa loại giữ chỗ. Vì vậy, RayDimkhông phụ thuộc vào loại.

"Phụ thuộc vào giá trị" được định nghĩa trong [temp.dep.constexpr] . Các trường hợp duy nhất có thể áp dụng cho một định danh đơn độc như RayDimtrong đoạn 2:

Một id thể hiện là giá trị phụ thuộc vào nếu:

  • nó phụ thuộc vào loại
  • nó là tên của một tham số mẫu không phải kiểu
  • nó đặt tên một thành viên dữ liệu tĩnh là thành viên phụ thuộc của khởi tạo hiện tại và không được khởi tạo trong một công cụ khai báo thành viên ,
  • nó đặt tên một hàm thành viên tĩnh là thành viên phụ thuộc của khởi tạo hiện tại, hoặc
  • nó là một hằng số với kiểu chữ và được khởi tạo với một biểu thức phụ thuộc vào giá trị.

Từ trên, RayDimkhông phụ thuộc vào loại. Nó chắc chắn không phải là một tham số mẫu hoặc chức năng thành viên. Nó là một thành viên dữ liệu tĩnh và thành viên phụ thuộc của khởi tạo hiện tại, nhưng nó được khởi tạo trong trình khai báo thành viên . Đó là, " = 3" xuất hiện bên trong định nghĩa lớp, không phải trong một định nghĩa thành viên riêng biệt. Nó là một hằng số với kiểu chữ, nhưng trình khởi tạo của nó 3không phụ thuộc vào giá trị.

Vì vậy, RayDimkhông phụ thuộc vào giá trị hoặc phụ thuộc vào loại. Do đó, Matrix<RayDim>không phải là một loại phụ thuộc, yF.headkhông phải là thành viên của một khởi tạo không xác định và templatetừ khóa trước headlà tùy chọn, không bắt buộc. (Nó được cho phép vì nó không nằm trong "ngữ cảnh chỉ loại" và headthực tế có tên là mẫu thành viên.)


0

Khước từ

đây không phải là một câu trả lời, mà là một bình luận dài

Kỹ năng của tôi quá thấp để có thể hiểu đầy đủ về tiêu chuẩn nhưng đây là một vài điều tôi phát hiện ra khi thử nghiệm mã. Tất cả mọi thứ tiếp theo đều dựa trên sự hiểu biết (không hoàn hảo) của tôi về vấn đề này và có lẽ cần một số đánh giá.

Cuộc điều tra

Trước hết, tôi đã đi đến một phiên bản tiêu chuẩn được xác định đầy đủ (C ++ 17) để chúng tôi hoạt động trên một triển khai được xác định rõ.

Nhìn vào này, có vẻ như MSVC vẫn còn một số vấn đề (với việc tra cứu, tôi đoán vậy?) Khi nói đến việc khởi tạo và xác định lại mẫu. Tôi sẽ không tin MSVC nhiều trong kịch bản của chúng tôi.

Điều đó nói rằng, hãy nghĩ về lý do tại sao chúng ta có thể cần templatetừ khóa tại

return yF.template head<1>();

Tên người phụ thuộc

Đôi khi, bên trong các mẫu, chúng ta phải giúp trình biên dịch quyết định xem tên có đề cập đến

  1. một giá trị int T::x = 0,
  2. một loại struct T::x {};, hoặc
  3. một mẫu template <typename U> T::foo<U>();

Nếu chúng ta đề cập đến một giá trị, chúng ta không làm gì cả. Nếu chúng ta đề cập đến một loại, chúng ta phải sử dụng typename. Và nếu chúng ta đề cập đến một mẫu chúng ta sử dụng template. Thêm về chủ đề đó có thể được tìm thấy ở đây .

Tôi không hiểu đặc điểm kỹ thuật tiêu chuẩn khi nói đến định nghĩa thực tế của tên phụ thuộc nhưng đây là một số quan sát.

Quan sát

Hãy xem mã tham chiếu

template <int N>
struct Matrix 
{
    template <int Idx>
    int head() { return Idx; }
};


template <typename T>
struct Test 
{    
    static constexpr int RayDim = 3;

    int func() const 
    {
        Matrix<RayDim> yF;
        return yF.head<1>(); // clang complains, gcc and msvc are ok
    }
};


struct Empty {};

int test() 
{
    Test<Empty> t;
    return t.func();
}

Thông thường RayDimnên là một tên phụ thuộc (vì nó nằm trong mẫu Test) sẽ gây ra Matrix<RayDim>một tên phụ thuộc. Bây giờ, hãy giả sử rằng Matrix<RayDim>thực sự là một tên phụ thuộc. Điều này làm cho Matrix<RayDim>::headmột tên phụ thuộc là tốt. Vì Matrix<RayDim>::headlà một hàm templated, bản thân nó là một khuôn mẫu và quy tắc của các tên phụ thuộc từ bên trên áp dụng, yêu cầu chúng tôi sử dụng templatetừ khóa. Đây là những gì clang đang phàn nàn về.

Tuy nhiên, vì RayDimđược định nghĩa bên trong Testfunccũng được định nghĩa bên trong cùng một khuôn mẫu và bản thân nó không phải là một hàm templated, tôi không nghĩ RayDimthực sự là một tên phụ thuộc trong ngữ cảnh func. Hơn nữa, RayDimkhông dựa vào các đối số mẫu của Test. Trong trường hợp này, Matrix<RayDim>Matrix<RayDim>::headtương ứng, sẽ trở thành tên không phụ thuộc, cho phép chúng ta bỏ qua templatetừ khóa. Đây là lý do tại sao gcc (và msvc) biên dịch.

Nếu chúng ta đã mẫu như RayDimlà tốt, như ở đây

template <typename>
static constexpr int RayDim = 3;

gcc sẽ coi nó như một tên phụ thuộc aswell (điều này là chính xác, vì có thể có một chuyên môn mẫu sau này vì vậy chúng tôi không biết tại thời điểm đó). Trong khi đó, msvc vui vẻ chấp nhận mọi thứ chúng tôi ném vào nó.

Phần kết luận

Có vẻ như nó đang sôi sục cho dù RayDimlà một tên phụ thuộc trong bối cảnh Test<T>::funchay không. Clang nghĩ rằng nó là, gcc không. Từ một số thử nghiệm khác, nó trông giống như các mặt msvc với tiếng kêu trên cái này. Nhưng đó cũng là việc làm của riêng nó, vậy ai biết?

Tôi sẽ đứng về phía gcc ở đây vì tôi thấy không có cách nào có thể RayDimtrở nên phụ thuộc vào thời điểm funcđược khởi tạo.

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.