9

Is there a way to static_assert that a type T is Not complete at that point in a header? The idea is to have a compile error if someone adds #includes down the road in places they should not be.

related: How to write `is_complete` template?

Using that link's answer,

namespace
{
template<class T, int discriminator>
struct is_complete {
  static T & getT();
  static char (& pass(T))[2];
  static char pass(...);
  static const bool value = sizeof(pass(getT()))==2;
};
}
#define IS_COMPLETE(X) is_complete<X,__COUNTER__>::value
class GType;
static_assert(!IS_COMPLETE(GType),"no cheating!");

unfortunately this gives "invalid use of incomlete type" error, d'oh. Is there a way to assert on the negation?

Community
  • 1
  • 1
peter karasev
  • 2,578
  • 1
  • 28
  • 38
  • 3
    Since you have C++11, you can use [expression SFINAE](http://coliru.stacked-crooked.com/a/8d5e54bafa436f5f). – chris Sep 11 '14 at 20:20
  • 1
    @chris: Why do you introduce the `declval` there? http://coliru.stacked-crooked.com/a/d2987f9901270a48 – Deduplicator Sep 11 '14 at 20:33
  • 1
    @Deduplicator, Change `S` to be `struct S {S(int){}};` and you'll get `0` by just using `S{}` instead of `std::declval()`. `std::declval` gives you an rvalue reference so you don't need to use constructors that may or may not exist. However, yes, it was all completely unnecessary in the first place because I'm dumb and `sizeof` works on types. – chris Sep 11 '14 at 20:39
  • Based on this: clang++ -std=c++1y -stdlib=libc++ -Wall -Wextra -pedantic- errors -O3 -pthread main.cpp /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && ./a.out #clang++ -E -P main.cpp #g++-4.9 -std=c++1y -Wall -Wextra -pedantic-errors -O3 -pthread main.cpp && ./a.out #clang -x c -std=c11 -Wall -Wextra -pedantic -O3 main.cpp && ./a.out 1 0 From the links posted, it seems I'm out of luck even with GCC 4.7x, let alone VS2012? Ok I'll file this under 'use in the future'! – peter karasev Sep 21 '14 at 05:18

2 Answers2

4

Here is a function using expression SFINAE based on chris proposal which allows checking whether a type is complete yet.
My adoption needs no includes, errors-out when the required argument is missing (hiding the argument was not possible) and is suitable for C++11 onward.

template<typename T>
constexpr auto is_complete(int=0) -> decltype(!sizeof(T)) {
    return true;   
}

template<typename T>
constexpr bool is_complete(...) {return false;}

And a test-suite:

struct S;

bool xyz() {return is_complete<S>(0);}

struct S{};

#include <iostream>
int main() {
    std::cout << is_complete<int>(0) << '\n';
    std::cout << xyz() << '\n';
    std::cout << is_complete<S>(0);
}

Output:

1
0
1

See live on coliru

Community
  • 1
  • 1
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • Of course it is possible to just make a wrapper: `constexpr bool foo() {return is_complete(0);}`. C++14 simplifies that to a variable template. – chris Sep 11 '14 at 21:19
  • @chris: Tried it, but that somehow made the second invocation equal to the first... – Deduplicator Sep 11 '14 at 21:21
  • @MarcGlisse: I don't think that's a violation of the ODR, the expression-SFINAE is only used to select the proper overload. – Deduplicator Sep 12 '14 at 19:16
  • is the constexpr 100% necessary? it would be nice if it can build in visual studio 2012 for the um, persons I'm trying to guard against violating the concept that motivates putting a compiler error here. – peter karasev Sep 21 '14 at 04:53
  • 1
    I don't think they are optional on this solution... unless you don't need compile-time constant-expressions, of course. – Deduplicator Sep 21 '14 at 10:08
2

Passing a reference through ... doesn't work.

5.2.2/7:

When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.10). [note skipped — n.m.] The lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the argument expression. An argument that has (possibly cv-qualified) type std::nullptr_t is converted to type void* (4.10). After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed.

Here's a kind-of-working solution hastily adapted from @chris's comment:

#include <iostream>
#include <utility>
namespace
{

    template<typename T, int>
        constexpr auto is_complete(int) -> decltype(sizeof(T),bool{}) {
            return true;
        }

    template<typename T, int>
        constexpr auto is_complete(...) -> bool {
            return false;
        }
}


#define IS_COMPLETE(T) is_complete<T,__LINE__>(0) // or use __COUNTER__ if supported

struct S;

static_assert(IS_COMPLETE(int), "oops 1!");
static_assert(!IS_COMPLETE(S), "oops 2!");

struct S {};

static_assert(IS_COMPLETE(S), "oops 3!");
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243