使用 sfinae 挑选出首选的可变参数构造函数
Use sfinae to single out preferred variadic constructor
我正在尝试编写一个基于策略的 class,将参数转发给它唯一的超级 class,但也可以选择采用它自己的一些参数。我面临的问题是,面对隐式转换(只有参数包),编译器似乎无条件地更喜欢下面的第二个构造函数,而不是第一个,这是我更喜欢的。
#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)
应该 select 第一个构造函数,而不是第二个。 b3
.
同上
构造函数上的 sfinae 让编译器在 2 与参数不匹配时选择 1,但我希望它在每次匹配时 select 构造函数 1。在 a1
的情况下,7 从 int
到 unsigned long
的隐式转换强制选择构造函数 2,我也不明白为什么。
我该如何解决这个问题?
这似乎是你想要的(尽管下面的评论指出它可能是一个无效的结构):
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) {
static_assert(std::is_same_v<Super*, void>);
}
// 2
template <typename... Args,
typename = std::enable_if_t<!std::is_constructible_v<Base, Args...>>>
explicit Base(Args&&... args) : Super(std::forward<Args>(args)...), count(0) {}
unsigned long count;
};
我喜欢 godbolt 运行 你的代码吧!
让我们收集需求:
- 第一个参数可以隐式转换为
unsigned long
,其余的可以构造 base => 这样做。
- 不适合 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;
};
请注意,两个模板化构造函数都是隐式转换的候选者。将 explicit
放在需要的地方作为 reader.
的练习
如果有更多的选择需要考虑,tag-dispatching是可取的:
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)...)
{}
我正在尝试编写一个基于策略的 class,将参数转发给它唯一的超级 class,但也可以选择采用它自己的一些参数。我面临的问题是,面对隐式转换(只有参数包),编译器似乎无条件地更喜欢下面的第二个构造函数,而不是第一个,这是我更喜欢的。
#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)
应该 select 第一个构造函数,而不是第二个。 b3
.
构造函数上的 sfinae 让编译器在 2 与参数不匹配时选择 1,但我希望它在每次匹配时 select 构造函数 1。在 a1
的情况下,7 从 int
到 unsigned long
的隐式转换强制选择构造函数 2,我也不明白为什么。
我该如何解决这个问题?
这似乎是你想要的(尽管下面的评论指出它可能是一个无效的结构):
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) {
static_assert(std::is_same_v<Super*, void>);
}
// 2
template <typename... Args,
typename = std::enable_if_t<!std::is_constructible_v<Base, Args...>>>
explicit Base(Args&&... args) : Super(std::forward<Args>(args)...), count(0) {}
unsigned long count;
};
我喜欢 godbolt 运行 你的代码吧!
让我们收集需求:
- 第一个参数可以隐式转换为
unsigned long
,其余的可以构造 base => 这样做。 - 不适合 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;
};
请注意,两个模板化构造函数都是隐式转换的候选者。将 explicit
放在需要的地方作为 reader.
如果有更多的选择需要考虑,tag-dispatching是可取的:
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)...)
{}