根据 class 的特征禁用 class 构造函数的正确方法
A proper way to disable a class constructor based on a trait of that class
我想 SFINAE 禁用 class 模板构造函数 - 但要注意的是,启用 - 禁用条件是 class 的特征。特别是,我只想在 class.
中存在与构造函数的模板参数匹配的特定 class 方法时才启用该构造函数
一个简化的例子:
struct S
{
std::size_t n = 0;
void set_value(int i) { n = i; }
void set_value(const std::string & s) { n = s.size(); }
template <class T, class = decltype(std::declval<S>().set_value(std::declval<T>()))>
S(T && x)
{
set_value(std::forward<T>(x));
}
};
该解决方案适用于 GCC,据称适用于 MSVC,但不适用于 Clang。在我看来 Clang 就在这里,因为在 class 定义中 class 尚未定义,所以 std::declval<S>()
不应该在这里工作。
所以我的想法是将该构造函数定义移出 class 定义:
struct S
{
std::size_t n = 0;
void set_value(int i) { n = i; }
void set_value(const std::string & s) { n = s.size(); }
template <class T, class>
S(T &&);
};
template <class T, class = decltype(std::declval<S>().set_value(std::declval<T>()))>
S::S(T && x)
{
set_value(std::forward<T>(x));
}
但这在 GCC 和 MSVC 中不起作用。该解决方案也看起来有些可疑,因为我首先无条件声明该构造函数,然后才引入 SFINAE。
我的问题是 - 是否有可能在 C++11/14/17 中解决该任务?一个特定的任务是 class 只有当它也有一个匹配的 setter 方法时才应该有一个构造函数。显然,可以为每个 setter 方法重载手动定义一个非模板构造函数,但这很乏味且难以维护。
但我也对更一般的任务感兴趣 - SFINAE - 通过 class 的特征启用构造函数或其他 class 方法(需要完整的 class 定义) .
此外,在我的示例中哪个编译器更合适 - GCC/MSVC 还是 Clang?
您可以引入另一个默认值为S
的模板参数,并将其用于SFINAE。例如
template <class T, class X = S, class = decltype(std::declval<X>().set_value(std::declval<T>()))>
S(T && x)
{
set_value(std::forward<T>(x));
}
关于哪个编译器是正确的问题,这是CWG1836
问题不在于 std::declval
:您可以将 std::declval<S>
与不完整的 S
一起使用,因为它 returns 一个 S&&
。问题是当您使用 std::declval<S>().set_value
时,因为 S
在那一刻不完整。
但是,有一个明确的例外:
the class type shall be complete unless the class member access appears in the definition of that class.
并且 class 成员访问 是否出现在 S
的定义中。
所以 gcc 和 msvc 允许它是正确的。
好像clang不适用CWG1836的决议。在那种情况下,它发现 std::declval<S>()
没有依赖类型,因此它可以立即评估成员访问 std::declval<S>().set_value
(但没有这样做)。
为了安抚clang,可以将其设为依赖类型。例如,由于您已经在模板中,您可以使它依赖于 T
是什么:
template<typename T, typename...>
struct dependent_type_identity {
using type = T;
};
template<typename T, typename... Dependence>
using dependent_type_identity_t = typename dependent_type_identity<T, Dependence...>::type;
struct S
{
std::size_t n = 0;
void set_value(int i) { n = i; }
void set_value(const std::string & s) { n = s.size(); }
template <class T, class = decltype(std::declval<dependent_type_identity_t<S, T>>().set_value(std::declval<T>()))>
S(T && x)
{
set_value(std::forward<T>(x));
}
};
或者做 做的事情,并使它依赖于另一个默认为 S
的模板参数(它仍然是一个依赖类型,即使没有办法改变它是什么设置为)
我想 SFINAE 禁用 class 模板构造函数 - 但要注意的是,启用 - 禁用条件是 class 的特征。特别是,我只想在 class.
中存在与构造函数的模板参数匹配的特定 class 方法时才启用该构造函数一个简化的例子:
struct S
{
std::size_t n = 0;
void set_value(int i) { n = i; }
void set_value(const std::string & s) { n = s.size(); }
template <class T, class = decltype(std::declval<S>().set_value(std::declval<T>()))>
S(T && x)
{
set_value(std::forward<T>(x));
}
};
该解决方案适用于 GCC,据称适用于 MSVC,但不适用于 Clang。在我看来 Clang 就在这里,因为在 class 定义中 class 尚未定义,所以 std::declval<S>()
不应该在这里工作。
所以我的想法是将该构造函数定义移出 class 定义:
struct S
{
std::size_t n = 0;
void set_value(int i) { n = i; }
void set_value(const std::string & s) { n = s.size(); }
template <class T, class>
S(T &&);
};
template <class T, class = decltype(std::declval<S>().set_value(std::declval<T>()))>
S::S(T && x)
{
set_value(std::forward<T>(x));
}
但这在 GCC 和 MSVC 中不起作用。该解决方案也看起来有些可疑,因为我首先无条件声明该构造函数,然后才引入 SFINAE。
我的问题是 - 是否有可能在 C++11/14/17 中解决该任务?一个特定的任务是 class 只有当它也有一个匹配的 setter 方法时才应该有一个构造函数。显然,可以为每个 setter 方法重载手动定义一个非模板构造函数,但这很乏味且难以维护。
但我也对更一般的任务感兴趣 - SFINAE - 通过 class 的特征启用构造函数或其他 class 方法(需要完整的 class 定义) .
此外,在我的示例中哪个编译器更合适 - GCC/MSVC 还是 Clang?
您可以引入另一个默认值为S
的模板参数,并将其用于SFINAE。例如
template <class T, class X = S, class = decltype(std::declval<X>().set_value(std::declval<T>()))>
S(T && x)
{
set_value(std::forward<T>(x));
}
关于哪个编译器是正确的问题,这是CWG1836
问题不在于 std::declval
:您可以将 std::declval<S>
与不完整的 S
一起使用,因为它 returns 一个 S&&
。问题是当您使用 std::declval<S>().set_value
时,因为 S
在那一刻不完整。
但是,有一个明确的例外:
the class type shall be complete unless the class member access appears in the definition of that class.
并且 class 成员访问 是否出现在 S
的定义中。
所以 gcc 和 msvc 允许它是正确的。
好像clang不适用CWG1836的决议。在那种情况下,它发现 std::declval<S>()
没有依赖类型,因此它可以立即评估成员访问 std::declval<S>().set_value
(但没有这样做)。
为了安抚clang,可以将其设为依赖类型。例如,由于您已经在模板中,您可以使它依赖于 T
是什么:
template<typename T, typename...>
struct dependent_type_identity {
using type = T;
};
template<typename T, typename... Dependence>
using dependent_type_identity_t = typename dependent_type_identity<T, Dependence...>::type;
struct S
{
std::size_t n = 0;
void set_value(int i) { n = i; }
void set_value(const std::string & s) { n = s.size(); }
template <class T, class = decltype(std::declval<dependent_type_identity_t<S, T>>().set_value(std::declval<T>()))>
S(T && x)
{
set_value(std::forward<T>(x));
}
};
或者做 S
的模板参数(它仍然是一个依赖类型,即使没有办法改变它是什么设置为)