简体   繁体   中英

Custom compile error message when undefined subtype is accessed

I have some types which have sub-types with the same name each:

struct TypeA {
    typedef int subtype;
};
struct TypeB {
    typedef float subtype;
};

and also types which don't have this sub-type but which are used in the same context:

struct TypeC {
    // (no subtype defined)
};

How can I add a dummy sub-type which gives a custom compile error message?

My (so far unsuccessful) attempt is:

struct TypeC {
    struct subtype {
        static_assert(false, "Attempt to access the non-existent subtype of TypeC.");
    };
};

But static_assert(false, ...) can't work, as the compiler throws the error even if the type is never accessed.

How can I delay the evaluation of static_assert to the time when the type is being accessed?

A failed attempt is to introduce a dummy enum and construct an expression out of it:

enum { X };
static_assert(X != X, "...");

Concrete use case: I have a class-template List which is defined with the sub-types head and tail if non-empty, and should give an error if these sub-types are used if it is empty:

template<typename...>
struct List;

// empty list:
template<>
struct List<> {
    struct head { static_assert(false, "Attempt to access the head of an empty list."); };
    struct tail { static_assert(false, "Attempt to access the tail of an empty list."); };
};

// non-empty list:
template<typename Head, typename ...Tail>
struct List<Head, Tail...> {
    typedef Head head;
    typedef List<Tail...> tail;
};

If I simply leave out the types head and tail , when accessing eg the 3rd element of a list which has size 2 with the code List<int,int>::tail::tail::head gives the not so nice message (g++ 4.7.2): 'head' is not a member of 'List<int>::tail {aka List<>}'

To delay the evaluation of static_assert to the point where your type is accessed, you have to make the expression depend on a template parameter .

One possible solution is to add a helper class template just for printing the error message conditionally (depending on the value of the template parameter):

template<bool X = false>
struct SubTypeErrorMessage {
    static_assert(X, "Attempt to access the non-existent subtype of TypeC.");
};

Then, in the concrete type where you want to have a "dummy sub-type":

struct TypeC {
    typedef SubTypeErrorMessage<> subtype;
};

Live Demo

// empty list:
template<typename... Args>
struct List {
    struct head {static_assert(sizeof...(Args) != 0, "Attempt to access the head of an empty list."); };
    struct tail {static_assert(sizeof...(Args) != 0, "Attempt to access the tail of an empty list."); };
};

// non-empty list:
template<typename Head, typename ...Tail>
struct List<Head, Tail...> {
    typedef Head head;
    typedef List<Tail...> tail;
};

Edit: This problem actually touches on three aspects of how C++ templates work:

  1. (§14.7.1 [temp.inst]/p1) Unless a class template specialization has been explicitly instantiated (14.7.2) or explicitly specialized (14.7.3), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program. The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions ... of the class member functions, member classes, [...].
  2. (§14.7.1 [temp.inst]/p11) An implementation shall not implicitly instantiate ... a member class...of a class template that does not require instantiation.
  3. (§14.6 [temp.res]/p8) If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.

3) means that the static_assert expression must depend on a template argument, as otherwise "no valid specialization" can be generated for the template and the program is ill-formed, and compilers are free to report an error (although they don't have to). In the above code, a valid specialization can be generated for the first template, but such a specialization is never used because of the partial specialization.

The solution given above also relies on 1) and 2). 1) provides that implicitly instantiating a template specialization only instantiates the declarations (not definitions ) of member classes, and 2) means that compilers are affirmatively prohibited from attempting to instantiate head or tail if one is merely using an implicitly instantiated List<> . Note that this rule does not apply if you explicitly instantiate List<> with template struct List<>; .

The solution in leemes's answer works because typedef s do not require a complete type and so do not trigger implicit instantiation of SubTypeErrorMessage<> under 1), and the use of a template argument in the static_assert in SubTypeErrorMessage bypasses 3), as a valid specialization (ie, SubTypeErrorMessage<true> ) can be generated for that template.

It's worth noting that in both cases the instantiation rules mean that it's still legal to use List<>::head or TypeC::subtype as long as you don't use them in a way that requires a complete type. Thus something like int f(List<>::head & ) { return 0; } int f(List<>::head & ) { return 0; } is valid, though entirely meaningless since there's no way you can actually call that function. If you don't define List<>::head at all, however, the compiler will report a (perhaps cryptic) error on this code. So that's the trade-off for prettier error messages :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM