使用static_assert

时间:2017-01-16 06:14:42

标签: c++ templates c++14 sfinae

我正在尝试创建一个将注册接收器的事件管理器。为此,我希望能够使用给定参数构造std::function。但是,我希望最终用户可以轻松理解错误。我想用SFINAE和类型依赖static_assert来做这件事,但我遇到了麻烦,因为这两个函数在有效输入上变得模糊不清。此外,我希望有多个错误原因,用户可以收到。由于存在两个故障点(提供无效的仿函数并提供错误的事件类型),我希望总共有3个函数,第一个是正确输入的函数,然后是不正确的输入滴漏(而不是有4个函数用于每个国家的组合)。

这可以用c ++ 17 if constexpr解决,但我的目标平台是c ++ 14,因此需要使用其他方法。

我当前的尝试(仅检查一个错误状态):

template <typename Event, typename Func>
auto register(Func &&func)
-> decltype(func_t<Event>(std::forward<Func>(func)), void()) {}

template <typename Event, typename Func>
void register(Func &&) {
    static_assert(meta::delay_v<Func>, "Function object cant be constructed by function");
}

meta::delay_v等于false,但取决于其参数,因此在调用函数之前不会触发static_assert

更复杂的用例是

template <typename Event, typename Func>
auto register(Func &&func)
-> decltype(func_t<Event>(std::forward<Func>(func))
            ,meta::is_in_tuple<Event, Events_Tuple>
            ,void()) {}

因此,如果第一次测试失败(func_t构造),那么我们会static_assert关于那个,如果第二次测试失败,我们会static_assert关于那个。因此,如果第一次测试失败,无论第二次测试如何,我们都会失败一些静态断言。然后,如果第一次测试通过,我们将打印关于第二次测试失败的信息。不必重写测试将是一个非常好的奖金。

3 个答案:

答案 0 :(得分:3)

当条件满足时,它们实际上是模糊的,因为两者都是有效的 只有第一个函数有一个可以禁用的sfinae表达式,因此第二个函数始终是一个可行的解决方案(当满足条件时这是一个模糊的解决方案)。

您可以这样做:

template <typename Event, typename Func>
auto register(int, Func &&func)
-> decltype(func_t<Event>(std::forward<Func>(func)), void()) {}

template <typename Event, typename Func>
void register(char, Func &&) {
    static_assert(meta::delay_v<Func>, "Function object cant be constructed by function");
}

template <typename Event, typename Func>
void register(Func &&func) {
    register<Event>(0, std::forward<Func>(func));
}

在这种情况下,0(即int)将强制编译器选择第一个函数并尝试它。如果它有效,则没有歧义(第二个需要char),否则0可以转换为char并用于调用第二个函数。

如果您有两个以上的条件,您可以这样做:

template<int N> struct tag: tag<N-1> {};
template<> struct tag<0> {};

template <typename Event, typename Func>
auto register(tag<2>, Func &&func)
-> decltype(func_t<Event>(std::forward<Func>(func)), void()) {}

template <typename Event, typename Func>
auto register(tag<1>, Func &&func)
-> decltype(func_alternative_t<Event>(std::forward<Func>(func)), void()) {}

template <typename Event, typename Func>
void register(tag<0>, Func &&) {
    static_assert(meta::delay_v<Func>, "Function object cant be constructed by function");
}

template <typename Event, typename Func>
void register(Func &&func) {
    register<Event>(tag<2>{}, std::forward<Func>(func));
}

解决方案的数量越多,标签使用的数量就越多。适用于int / char法案的相同原则适用于此处。

作为旁注,正如@StoryTeller在评论中所提到的,请注意register是保留关键字,您不应在生产代码中使用它。

答案 1 :(得分:2)

经过一番思考,我发现了另一种看起来更好的方法。

template <typename Func, typename... Ts>
decltype(auto) evaluate_detector(std::true_type, Func && f, Ts&&...) {
    return f(true);
}

template <typename Func, typename... Ts>
decltype(auto) evaluate_detector(std::false_type, Func &&, Ts&&... ts) {
    return evaluate_detector(std::forward<Ts>(ts)...);
}

template <typename Event, typename Func>
void register(Func &&func) {
    using can_construct = std::is_constructable<func_t<Event>, Func>;
    using proper_event = meta::is_in_tuple<Event, Events_Tuple>;
    evaluate_detector(meta::and<can_construct, proper_event>{},
        [&](auto){/*do proper thing*/};
        meta::not<can_construct>{},
        [](auto delay){static_assert(meta::delay_v<decltype(delay)>, "can't construct"},
        meta::not<proper_event>{},
        [](auto delay){static_assert(meta::delay_v<decltype(delay)>, "improper event"});
}

优点是在一个中心位置具有所有错误状态,而不必创建许多覆盖功能。这就是我设想的探测器习语的用法。 can_constructproper_event的类型评估为std::true_typestd::false_type,或继承这些类型的内容,因此我们仍然有重载解析但以通用方式完成。

答案 2 :(得分:1)

注意:这开头是对the OP's answer的评论,但有点大;道歉是为了衍生。

我建议进行以下重组:

namespace detail {
    template<typename PredT, typename F>
    struct fail_cond {
        using pred_type = PredT;
        F callback;
    };
    struct success_tag { };

    template<typename F>
    constexpr decltype(auto) eval_if(int, fail_cond<success_tag, F>&& fc) {
        return fc.callback();
    }

    template<
        typename FC, typename... FCs,
        typename PredT = typename std::decay_t<FC>::pred_type,
        std::enable_if_t<std::is_base_of<std::false_type, PredT>{}, int> = 0
    >
    constexpr decltype(auto) eval_if(int, FC&& fc, FCs&&...) {
        return fc.callback(PredT{});
    }

    template<typename FC, typename... FCs>
    constexpr decltype(auto) eval_if(long, FC&&, FCs&&... fcs) {
        return detail::eval_if(0, std::move(fcs)...);
    }
}

template<typename PredT, typename F, typename = std::result_of_t<F&(std::true_type)>>
constexpr detail::fail_cond<PredT, F> fail_cond(F&& failure_cb) {
    return {std::forward<F>(failure_cb)};
}

template<typename F, typename... PredTs, typename... Fs>
constexpr decltype(auto) eval_if(F&& success_cb, detail::fail_cond<PredTs, Fs>&&... fcs) {
    return detail::eval_if(
        0, std::move(fcs)...,
        detail::fail_cond<detail::success_tag, F>{std::forward<F>(success_cb)}
    );
}

现在用法如下:

template<typename Event, typename Func>
decltype(auto) register(Func&& func) {
   using can_construct = std::is_constructible<func_t<Event>, Func&&>;
   using proper_event = meta::is_in_tuple<Event, Events_Tuple>;
   return eval_if(
      [&]() { /*do proper thing*/ },
      fail_cond<can_construct>([](auto pred) { static_assert(pred, "can't construct"); }),
      fail_cond<proper_event>([](auto pred) { static_assert(pred, "improper event"); })
   );
}

// or ...

template<typename Event, typename Func>
decltype(auto) register(Func&& func) {
   return eval_if(
      [&]() { /*do proper thing*/ },
      fail_cond<std::is_constructible<func_t<Event>, Func&&>>(
          [](auto pred) { static_assert(pred, "can't construct"); }
      ),
      fail_cond<meta::is_in_tuple<Event, Events_Tuple>>(
          [](auto pred) { static_assert(pred, "improper event"); }
      )
   );
}

Online Demo

该演示虽然痛苦地设计,但在编译时运行时(n.b.故障回调可以返回值)显示出失败行为的可能性。还演示了传递给失败回调的值是失败的谓词的实例,这允许潜在更丰富的失败行为并减少static_assert所需的样板。