类继承的static_assert以终止编译(通过SFINAE?)

时间:2017-09-22 06:58:06

标签: c++11 sfinae static-assert

目标

假设template <class T> class MyThing包含大量using语句,例如using T::my_defusing T::my_other_def。出于显而易见的原因,如果T未定义my_defmy_other_def,则编译将失败。 然而,实际用例中的编译失败导致数百个错误,使用户难以理解真正的问题。虽然编译失败已经足够好了,但我真的很想通过某种聪明的代理类找到一种方法来提前终止编译。首先,为了完整性,包括SFINAE设置。第二个是如何使用函数完成此操作的必然结果示例。然后我的尝试,并怀疑为什么这可能实际上不可能(但我希望是错的)。

注意:这是严格的&#34;&#34; C ++ 11项目(但我并不反对导入类型,例如std::void_t已成功,但我在std::conditional失败了。如果您了解这些内容,请使用note:

切换到 2 部分
  • is_valid<T>()static constexpr bool函数,其行为与std::conditional类似,检查my_defmy_other_defT中的有效类型名称。

1:SFINAE设置(C ++ 17 backports和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。功能版本

这仅适用于调用函数。这种方法(据我所知)不能用于验证类,因为编译仍将继续。 &#34;技巧&#34;这里只是为函数调用添加了足够的间接层。我无法弄清楚如何通过继承来做到这一点。

///////////////////////////////////////////////////////
// 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确实失败了,但是&#34;暴露了&#34; 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保留功能版本取消注释,您可以观察到我试图避免的相同行为。

  1. 触发了static_assert的{​​{1}}。
  2. 由于我们处于相同的代码块中,validate_wrapper<Almost_1>仍然会被编译并产生更多警告。
    • 如果您发表评论TheClass<Almost_1>,则表示您不会收到来自TheClass<Almost_1>的警告。
    • 类版本的类比是do_actual_work目前正在编译,所以它会一直看到它(根据我的理解,这是SFINAE的整点)。所以,由于main已经开始编译,所以无论基类中的TheClass<Almost_1>是什么,它都会一直持续到最后。
  3. 是否有任何推迟继承的聪明方法?我尝试了curiously recurring template pattern思考的一个变体,就是如何做到这一点,但它实际上最终产生了更多的警告。

1 个答案:

答案 0 :(得分:0)

希望还有另一种方法,但似乎最简单的解决方案是采用另一种方式:用户应该实例化的类没有实际的实现,internal类可以完成所有操作。

namespace internal {
    template <class T, bool valid>
    struct TheClass {
        static_assert(valid, "Template 'class T' must have static types `my_def` and `my_other_def`.");
    };

    template <class T>
    struct TheClass<T, true> {
        // 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 "work gets done";
        }
    };
}

template <class T>
struct TheClass : internal::TheClass<T, is_valid<T>()> { };

struct Good {
    using my_def = float;
    using my_other_def = int;
};
struct Almost_1 { using my_def = double; };

int main(int argc, const char **argv) {
    TheClass<Good> g;
    std::cout << "Good message: " << g.message() << std::endl;

    // only one static assert
    TheClass<Almost_1> a1;
    return 0;
}