The goal
Suppose a template <class T> class MyThing
has a large number of using
statements such as using T::my_def
or using T::my_other_def
. For obvious reasons, if T
does not define my_def
and my_other_def
, compilation will fail. However, compilation failure in the real use-case results in hundreds of errors that make it difficult for the user to understand the real problem. While compilation failure is "good enough", I'd really like to find a way to terminate compilation early through some sort of clever proxy class. First, for completeness, the SFINAE setup is included. Second is a corollary example of how doing this with functions can be done. Then my attempts, and suspected reason for why this may not actually be possible (but am hoping to be wrong).
Note: This is a "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
). If you know these things, kip to section 2 with note:
- The
is_valid<T>()
is astatic constexpr bool
function that behaves similar tostd::conditional
, checking thatmy_def
andmy_other_def
are valid typenames inT
.
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: https://stackoverflow.com/a/28253503/3814202
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. The function version
This only works for calling functions. This approach (to my knowledge) cannot be used for validating the classes, because compilation will still continue. The "trick" here is just adding enough layers of indirection to function calls. I can't figure out how to do this with inheritance.
///////////////////////////////////////////////////////
// Function example
// does the real work
template <class T>
typename std::enable_if< is_valid<T>(), void>::type
do_actual_work() {
// this never gets compiled from validate_wrapper<Almost_1>
// and therefore never produces compiler warnings about
// `my_other_def` not being a type
using my_def = typename T::my_def;
using my_other_def = typename T::my_other_def;
my_def x = (my_def) 11;
my_other_def y = (my_other_def) 12;
std::cout << "A my_def: " << x << std::endl
<< "A my_other_def: " << y << std::endl;
}
template <class T>
typename std::enable_if< !is_valid<T>(), void>::type
validate() {
// Compilation ends here
static_assert(is_valid<T>(), "You have been asserted.");
}
template <class T>
typename std::enable_if< is_valid<T>(), void>::type
validate() {
std::cout << "Work gets done." << std::endl;
do_actual_work<T>();
}
// user calls this function
template <class T>
void validate_wrapper() {
validate<T>();
}
See section 4 for the definition of classes that demonstrate this working.
3. 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();
}
};
4. 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;
// in function land, uncomment these and it will
// only produce the single static_assert for valid<T>
// rather than continuing on for breaking T
// validate_wrapper<Good>();
// validate_wrapper<Almost_1>();
// 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
// comment out to see that `validate_wrapper<Almost_1>` will
// terminate early (as desired)
TheClass<Almost_1> a1;
return 0;
}
So in the end, if you leave the function version uncommented in main
, you can observe the same behavior that I'm trying to avoid.
- The
static_assert
forvalidate_wrapper<Almost_1>
is triggered. - Since we are in the same code block,
TheClass<Almost_1>
is still going to get compiled and produce more warnings.- If you comment out
TheClass<Almost_1>
, you won't get warnings fromdo_actual_work
. - The analogy for the class version is that
main
is currently being compiled, so it's going to see it through to the end (kind of the whole point of SFINAE as I understand it). So sinceTheClass<Almost_1>
has already started to compile, it goes until the end regardless of thestatic_assert
going on in its base class.
- If you comment out
Is there any clever way of deferring inheritance? I tried a variant of the curiously recurring template pattern thinking that would be how to do this, but it actually ended up just producing more warnings.