使用sfinae筛选出首选的可变参数构造函数

埃尔布鲁诺夫斯基

我正在尝试编写一个基于策略的类,该类将参数转发到其唯一的超类,但也可以选择采用自己的一些参数。我面临的问题是,面对隐式转换(仅带有参数包),编译器似乎无条件地偏爱下面的第二个构造函数,而不是第一个构造函数。

#include <utility>
#include <iostream>

template <class Super>
struct Base : public Super {
  // 1
  template <typename... Args,
            typename = std::enable_if_t<std::is_constructible_v<Super, Args&&...>>>
  explicit Base(unsigned long count, Args&&... args)
    : Super(std::forward<Args>(args)...), count(count) {}

  // 2
  template <typename... Args,
            typename = std::enable_if_t<std::is_constructible_v<Super, Args&&...>>>
  explicit Base(Args&&... args) : Super(std::forward<Args>(args)...), count(0) {}

  unsigned long count;
};

struct A {
  explicit A(unsigned long id) {}
  A() {}
};

struct B {
  explicit B(const char* cstring) {}
  explicit B(unsigned long id, const char* cstring) {}
  explicit B(unsigned long id, A a) {}
  B() {}
};

int main() {
  auto a1 = Base<A>(7);             // selects 2, but I want 1
  auto a2 = Base<A>(7ul);           // selects 1
  auto a3 = Base<A>(7, 10);         // selects 1
  auto b1 = Base<B>(4);             // selects 1
  auto b2 = Base<B>("0440");        // selects 2
  auto b3 = Base<B>(4, "0440");     // selects 2, but I want 1
  auto b4 = Base<B>(4, 4, "0440");  // selects 1
  auto b5 = Base<B>(4, A());        // selects 2
  std::printf("%lu %lu %lu\n", a1.count, a2.count, a3.count);
  std::printf("%lu %lu %lu %lu %lu\n", b1.count, b2.count, b3.count, b4.count, b5.count);
  return 0;
}

输出0 7 7在第一行上,但是我想要7 7 7,即Base<A>(7)应该选择第一个构造函数,而不是第二个。同上b3

构造函数上的自定义函数允许编译器在2与参数不匹配时选择1,但我希望它每次匹配时都选择构造函数1。在这种a1情况下,从7int的隐式转换unsigned long迫使选择构造函数2,我也不明白为什么。

我该如何解决呢?

重复数据删除器

让我们收集需求:

  1. 第一个隐式可转换为的参数unsigned long,rest可以构造base =>做到这一点。
  2. 不适合1,并且参数可以构造base =>来做到这一点。
struct Base : Super {
    // This one should be preferred:
    template <class... Args, class = std::enable_if_t<std::is_constructible_v<Super, Args...>>>
    explicit Base(unsigned long count = 0, Args&&... args)
    : Super(std::forward<Args>(args))
    , count(count) {
    }

    // Only if the first is non-viable:
    template <class U, class... Args, class = std::enable_if_t<
        !(std::is_convertible_v<U, unsigned long> && std::is_constructible_v<Super, Args...>)
        && std::is_constructible_v<Super, U, Args>>>
    explicit Base(U&& u, Args&&... args)
    : Base(0, std::forward<U>(u), std::forward<Args>(args)...) {
    }
    unsigned long count;
};

注意两个模板化的ctor都是隐式转换的候选对象。放置explicit在需要的地方作为练习。

如果要考虑更多替代方法,建议使用标签分派:

template <std::size_t N> struct priority : priority<N - 1> {};
template <> struct priority<0> {};

template <class... Ts>
static constexpr bool has_priority_v = (std::is_base_of_v<priority<0>, std::decay_t<Ts>> || ...);

class Base : Super {
    template <class UL, class... Ts, class = std::enable_if_t<
        std::is_convertible_v<UL, unsigned long> && std::is_constructible_v<Super, Ts...>>>
    Base(priority<1>, UL&& count, Ts&&... ts)
    : Super(std::forward<Ts>(ts)...), count(count)
    {}

    template <class... Ts, class = std::enable_if_t<std::is_constructible_v<Super, Ts...>>>
    Base(priority<0>, Ts&&... ts)
    : Base(priority<1>(), std::forward<Ts>(ts)...)
    {}
public:
    template <class... Ts, class = std::enable_if_t<
        !has_priority<Ts...> && std::is_constructible_v<Base, priority<>, Ts...>>>
    explicit Base(Ts&&... ts)
    : Base(priority<1>(), std::forward<Ts>(ts)...)
    {}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章