vendredi 22 septembre 2017

static_assert on class inheritance to terminate compilation (via SFINAE?)

I feel like this should be possible. I've figured out how to do it with functions (static functions of a proxy struct, or std::enable_if hacks on return type both work), but I can't figure out how to do it with classes. The approach used here is what worked for me with functions: template <class Actual, bool valid> with a static_assert; in <Actual, true> specialization the real code continues.

I'm going to include everything relevant for this "strictly" C++11 project (but I'm not opposed to importing types, e.g. std::void_t has been successful though I failed at std::conditional). Skip to section 2 with note:

  • The is_valid<T>() is a static constexpr bool function that behaves similar to std::conditional, checking that my_def and my_other_def are valid typenames in T.

1: SFINAE setup (C++17 backports and goodies)

///////////////////////////////////////////////////////
// backport from C++17 for using in C++11
namespace internal { template <class...> using void_t = void; }
///////////////////////////////////////////////////////
// my_def checker
/* primary template handles types that have no nested ::my_def member: */
template <class, class = internal::void_t<>>
struct has_my_def : std::false_type { };
/* specialization recognizes types that do have a nested ::my_def member: */
template <class T>
struct has_my_def<T, internal::void_t<typename T::my_def>> : std::true_type { };
///////////////////////////////////////////////////////
// my_other_def checker
/* primary template handles types that have no nested ::my_other_def member: */
template <class, class = internal::void_t<>>
struct has_my_other_def : std::false_type { };
/* specialization recognizes types that do have a nested ::my_other_def member: */
template <class T>
struct has_my_other_def<T, internal::void_t<typename T::my_other_def>> : std::true_type { };
///////////////////////////////////////////////////////
// freaking sweet: http://ift.tt/2ffCFIx
template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;

template <class T>
static constexpr bool is_valid() {
    return all_true< has_my_def<T>::value, has_my_other_def<T>::value >::value;
}

2. Attempted inheritance setup

The static_assert does go down, but the "exposed" TheClass (not internal::TheClass) will then continue on to try the using statements. For the real intent of this code, this creates hundreds of warnings which is why I'm trying to do this.

///////////////////////////////////////////////////////
// Need some more proxying?
namespace internal {
    template <class T, bool valid>
    struct TheClass {
        // compiler warning 1 (seek termination here)
        static_assert(valid, "You have been asserted.");
    };

    template <class T>
    struct TheClass<T, true> {
        std::string message() { return "You are valid."; }
    };
}

template <class T>
struct TheClass : public internal::TheClass<T, is_valid<T>()> {
    // compiler warning 2
    using my_def = typename T::my_def;
    // compiler warning 3
    using my_other_def = typename T::my_other_def;

    std::string message() {
        return internal::TheClass<T, is_valid<T>()>::message();
    }
};

3. some simple tests

///////////////////////////////////////////////////////
// The tests
struct Good {
    using my_def = float;
    using my_other_def = int;
};
struct Almost_1 { using my_def = double; };
struct Almost_2 { using my_other_def = bool; };
struct Bad {};

int main(int argc, const char **argv) {
    // sanity checks
    std::cout << std::boolalpha
              << "Good:     " << is_valid<Good>()     << std::endl
              << "Almost_1: " << is_valid<Almost_1>() << std::endl
              << "Almost_2: " << is_valid<Almost_2>() << std::endl
              << "Bad:      " << is_valid<Bad>()      << std::endl;

    // At least this works.
    TheClass<Good> g;
    std::cout << "Good message: " << g.message() << std::endl;

    // I would like to achieve just one error from static_assert
    TheClass<Almost_1> a1;
    return 0;
}

I've trolled every SO post I can find, but am likely not using the right terminology. Sorry if this has been answered, thank you for any suggestions on alternative design patterns (other than an object factory for complicated reasons).

Alternatively, is this not possible to do in any way?

Aucun commentaire:

Enregistrer un commentaire