我需要一种简单的方法来获取类T
对象的计数/长度/大小,其中T
是某种集合类型,例如std::map
,{{ 1}},std::list
,std::vector
,CStringArray
,CString
,...
对于大多数标准类型,std::string
是正确的答案,因为大多数MFC类T::size()
是正确的,对于T::GetSize()
,它是CString
。< / p>
我希望有一个:
T::GetLength()
...评估正确的成员函数调用。
似乎应该有一种简单的方法来调用template <typename T> auto size(const T & t)
上具有T
成员的特征模板,该成员本身使用SFINAE存在或不存在,如果它存在,那么它根据定义,调用适当的size(const T & t)
来返回t.size_function()
实例中元素的数量。
我可以编写一个精心设计的T
类型 - 特征模板 - 在stackoverflow上有一些例子 - 所有这些例子都让我感到非常困惑&#34;必须有一个更简单的方法&#34;。使用C ++ 17,似乎应该轻松优雅地解决这个问题?
这些讨论here和here似乎使用了一个不优雅的解决方案,其中一些答案使用预处理器宏来完成工作。这仍然是必要的吗?
但是......当然,必须有一种方法可以使用这样一个事实,即在has_member
上调用正确的成员函数是可编译的,并且调用错误的成员函数无法编译 - 这不可能是使用直接为给定类型T
创建正确的类型特征包装?
我想要的是:
T
选择template <typename T>
auto size(const T & collection)
{
return collection_traits<T>::count(collection);
}
的确切特化是因为它是唯一适合collection_traits<T>
的特殊化(即它调用正确的实例方法)。
答案 0 :(得分:31)
您可以使用expression SFINAE和多次重载。
这个想法如下:检查x.size()
是否是您的类型的有效表达式 - 如果是,则调用并返回它。重复.getSize
和.getLength
。
假设:
struct A { int size() const { return 42; } };
struct B { int getSize() const { return 42; } };
struct C { int GetLength() const { return 42; } };
您可以提供:
template <typename T>
auto size(const T& x) -> decltype(x.size()) { return x.size(); }
template <typename T>
auto size(const T& x) -> decltype(x.getSize()) { return x.getSize(); }
template <typename T>
auto size(const T& x) -> decltype(x.GetLength()) { return x.GetLength(); }
用法:
int main()
{
size(A{});
size(B{});
size(C{});
}
此解决方案易于扩展,可与使用模板化的容器无缝协作。
如果某种类型暴露了两个吸气剂会怎么样?
上述解决方案会导致歧义,但通过引入解决此问题的排名/排序很容易解决。
首先,我们可以创建一个rank
类,允许我们任意设置重载的优先级:
template <int N> struct rank : rank<N - 1> { };
template <> struct rank<0> { };
rank<N>
可隐式转换为rank<N - 1>
。在重载解析期间,完全匹配优于转换链。
然后我们可以创建size_impl
重载的层次结构:
template <typename T>
auto size_impl(const T& x, rank<2>)
-> decltype(x.size()) { return x.size(); }
template <typename T>
auto size_impl(const T& x, rank<1>)
-> decltype(x.getSize()) { return x.getSize(); }
template <typename T>
auto size_impl(const T& x, rank<0>)
-> decltype(x.GetLength()) { return x.GetLength(); }
最后,我们提供了一个接口函数,它开始调度到正确的size_impl
重载:
template <typename T>
auto size(const T& x) -> decltype(size_impl(x, rank<2>{}))
{
return size_impl(x, rank<2>{});
}
使用类似D
以下的类型
struct D
{
int size() const { return 42; }
int getSize() const { return 42; }
int GetLength() const { return 42; }
};
现在将选择rank<2>
的{{1}}重载:
答案 1 :(得分:9)
最简单的解决方案IMO是函数重载。
// Default implementation for std containers.
template <typename Container>
std::size_t size(Container const& c) { return c.size(); }
// Overloads for others.
std::size_t size(CStringArray const& c) { return c.GetSize(); }
std::size_t size(CString const& c) { return c.GetLength(); }
// ... etc.
答案 2 :(得分:2)
您需要expression SFINAE,并且您必须与可能决定符合这两个界面的其他类型玩得很好,所以请研究std::size()
。
目标是扩充std::size()
以处理至少遵循其中一个约定的所有类型,只要它们不会试图遵循其中的任何约定。
#include <type_traits>
#include <iterator>
namespace internal {
// Avoid conflict with std::size()
template <class C>
auto size_impl(const C& c, int) -> decltype((void)c.size());
// Avoid conflict with std::size()
template <class T, std::size_t N>
void size_impl(const T (&array)[N], int);
template <class C>
constexpr auto size_impl(const C& c, long)
noexcept(noexcept(c.GetLength()))
-> decltype(c.GetLength())
{ return c.GetLength(); }
template <class C>
constexpr auto size_impl(const C& c, long long)
noexcept(noexcept(c.getSize()))
-> decltype(c.getSize())
{ return c.getSize(); }
};
template <class T>
using enable_if_not_void_t = std::enable_if_t<!std::is_void<T>(), T>;
using std::size;
template <class C>
constexpr auto size(const C& c)
noexcept(noexcept(internal::size_impl(c, 0)))
-> enable_if_not_void_t<decltype(internal::size_impl(c, 0))>
{ return internal::size_impl(c, 0); }
使用模板和继承扩展内容可以获得任意级别的优先级:
template <std::size_t N>
struct priority : priority<N - 1> {};
template <>
struct priority<0> {};
类似于提议的Abbreviated Lambdas for Fun and Profit会大大简化事情。