【发布时间】:2018-03-03 16:10:00
【问题描述】:
目标
假设template <class T> class MyThing 有大量using 语句,例如using T::my_def 或using T::my_other_def。由于显而易见的原因,如果T 没有定义my_def 和my_other_def,编译将失败。 然而,实际用例中的编译失败会导致数百个错误,使用户难以理解真正的问题。虽然编译失败“足够好”,但我真的很想通过某种聪明的代理类找到一种提前终止编译的方法。首先,为了完整起见,包含了 SFINAE 设置。其次是一个推论示例,说明如何使用 functions 进行此操作。然后是我的尝试,以及这可能实际上不可能的原因(但我希望是错误的)。
注意:这是一个“严格”的 C++11 项目(但我不反对导入类型,例如 std::void_t 已经成功,虽然我在 std::conditional 失败了)。如果您知道这些,请跳到第 2 部分并附注:
-
is_valid<T>()是一个static constexpr bool函数,其行为类似于std::conditional,检查my_def和my_other_def是T中的有效类型名。
1:SFINAE 设置(C++17 反向移植和好东西)
///////////////////////////////////////////////////////
// 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。功能版本
这仅适用于调用函数。这种方法(据我所知)不能用于验证类,因为编译仍将继续。这里的“技巧”只是为函数调用添加足够的间接层。我不知道如何通过继承来做到这一点。
///////////////////////////////////////////////////////
// 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>();
}
请参阅第 4 节了解演示此工作的类的定义。
3。尝试继承设置
static_assert 确实下降了,但“暴露”的TheClass(不是internal::TheClass)将继续尝试using 语句。对于这段代码的真正意图,这会产生数百个警告,这就是我尝试这样做的原因。
///////////////////////////////////////////////////////
// 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。一些简单的测试
///////////////////////////////////////////////////////
// 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;
}
所以最后,如果您在main 中不注释函数版本,您可以观察到我试图避免的相同行为。
-
validate_wrapper<Almost_1>的static_assert被触发。 - 由于我们在同一个代码块中,
TheClass<Almost_1>仍将被编译并产生更多警告。- 如果您注释掉
TheClass<Almost_1>,您将不会收到来自do_actual_work的警告。 - 类版本的类比是
main当前正在编译,所以它会一直看到它(我理解的 SFINAE 的全部意义)。因此,由于TheClass<Almost_1>已经开始编译,所以不管static_assert在其基类中是否继续,它都会一直编译到最后。
- 如果您注释掉
有什么巧妙的延迟继承的方法吗?我尝试了curiously recurring template pattern 的一种变体,认为这将是如何做到这一点,但实际上它最终只是产生了更多警告。
【问题讨论】:
-
我可能处于愚蠢的模式,但我找不到你在哪里陈述了你真正想做的事情......
-
不,是我!我想尽办法让问题尽可能简短,这样做有效地消除了实际问题。通过将其与功能版本的工作方式进行比较,最后的问题引导和概要现在应该使事情更清楚了。
标签: c++11 sfinae static-assert