zha*_*nou 5 c++ overloading tuples sfinae
考虑一个包含两种类型 -FirstObjects和 的结构SecondObjects,它们都是std::tuple<>,例如:
struct Objects
{
using FirstObjects = std::tuple<int, int>;
using SecondObjects = std::tuple<int>;
};
Run Code Online (Sandbox Code Playgroud)
为了方便我们添加以下枚举:
enum class ObjectCategory
{
FIRST,
SECOND
};
Run Code Online (Sandbox Code Playgroud)
现在考虑以下在Objects如上所述的类型上模板化的类(唯一的约定是它具有FirstObjects和SecondObjects且它们是std::tuple):
template <typename T>
class SomeClass
{
public:
using FirstObjects = typename T::FirstObjects;
using SecondObjects = typename T::SecondObjects;
template <std::size_t Idx>
using FirstObject = typename std::tuple_element<Idx, FirstObjects>::type;
template <std::size_t Idx>
using SecondObject = typename std::tuple_element<Idx, SecondObjects>::type;
template <ObjectCategory Category, std::size_t Idx>
using ObjectType = std::conditional_t<Category == ObjectCategory::FIRST, FirstObject<Idx>, SecondObject<Idx>>;
template <ObjectCategory Category, std::size_t Idx>
std::enable_if_t<Category == ObjectCategory::FIRST, const ObjectType<Category, Idx>>& getObject()
{
return std::get<Idx>(firstObjects_);
}
template <ObjectCategory Category, std::size_t Idx>
std::enable_if_t<Category == ObjectCategory::SECOND, const ObjectType<Category, Idx>>& getObject()
{
return std::get<Idx>(secondObjects_);
}
template <ObjectCategory Category, std::size_t Idx>
void doSomething()
{
const ObjectType<Category, Idx>& obj = getObject<Category, Idx>();
}
private:
FirstObjects firstObjects_;
SecondObjects secondObjects_;
};
Run Code Online (Sandbox Code Playgroud)
简而言之,SomeClass托管两个类型的成员变量,firstObjects_类型为T::FirstObjects,secondObjects_类型为T::SecondObjects,并且具有一个doSomething()函数,该函数使用 SFINAE 重载的getObject()getter 方法,该方法根据所选内容返回包含在firstObjects_或secondObjects_中的第i个对象ObjectCategory。
我现在遇到的问题是:
int main()
{
struct Objects
{
using FirstObjects = std::tuple<int, int>;
using SecondObjects = std::tuple<int>;
};
SomeClass<Objects> object{};
object.doSomething<ObjectCategory::FIRST, 0>(); // <------ works fine
object.doSomething<ObjectCategory::FIRST, 1>(); // <------ compiler error
}
Run Code Online (Sandbox Code Playgroud)
最后一行让编译器抱怨:
/usr/include/c++/4.9/tuple: In instantiation of 'struct std::tuple_element<1ul, std::tuple<int> >':
70:114: required by substitution of 'template<class T> template<ObjectCategory Category, long unsigned int Idx> using ObjectType = std::conditional_t<(Category == FIRST), typename std::tuple_element<Idx, typename T::FirstObjects>::type, typename std::tuple_element<Idx, typename T::SecondObjects>::type> [with ObjectCategory Category = (ObjectCategory)0; long unsigned int Idx = 1ul; T = main()::Objects]'
87:42: required from 'void SomeClass<T>::doSomething() [with ObjectCategory Category = (ObjectCategory)0; long unsigned int Idx = 1ul; T = main()::Objects]'
104:50: required from here
/usr/include/c++/4.9/tuple:682:12: error: invalid use of incomplete type 'struct std::tuple_element<0ul, std::tuple<> >'
struct tuple_element<__i, tuple<_Head, _Tail...> >
^
In file included from /usr/include/c++/4.9/tuple:38:0,
from 4:
/usr/include/c++/4.9/utility:85:11: error: declaration of 'struct std::tuple_element<0ul, std::tuple<> >'
class tuple_element;
^
In substitution of 'template<class T> template<ObjectCategory Category, long unsigned int Idx> using ObjectType = std::conditional_t<(Category == FIRST), typename std::tuple_element<Idx, typename T::FirstObjects>::type, typename std::tuple_element<Idx, typename T::SecondObjects>::type> [with ObjectCategory Category = (ObjectCategory)0; long unsigned int Idx = 1ul; T = main()::Objects]':
87:42: required from 'void SomeClass<T>::doSomething() [with ObjectCategory Category = (ObjectCategory)0; long unsigned int Idx = 1ul; T = main()::Objects]'
104:50: required from here
70:114: error: no type named 'type' in 'struct std::tuple_element<1ul, std::tuple<int> >'
Run Code Online (Sandbox Code Playgroud)
它似乎不喜欢SecondObjects只有一个元素因此std::get<1>(secondObjects_)不起作用的事实?但为什么这件事一开始就发生了,因为我已经打电话doSomething了ObjectCategory::FIRST,但没有呢ObjectCategory::SECOND?
如何解决这个问题?
------ 编辑 1 ------
@Taekahn指出的解决方案是将getObject()方法更改如下:
template <ObjectCategory Category, std::size_t Idx>
const auto& getObject()
{
return std::get<Idx>(firstObjects_);
}
template <ObjectCategory Category, std::size_t Idx, typename = std::enable_if_t<Category == ObjectCategory::SECOND>>
const auto& getObject()
{
return std::get<Idx>(secondObjects_);
}
Run Code Online (Sandbox Code Playgroud)
上面的方法似乎有效,因为当使用关键字而auto不是显式类型时,编译器不会评估/检查已被 SFINAE 删除的方法的返回类型的有效性。但这只是推测 - 非常感谢知识渊博的 C++ 从业者在这里权衡!
------ 编辑 2 ------
当尝试这样做时,上述结果会产生歧义
object.doSomething<ObjectCategory::SECOND, 0>();
Run Code Online (Sandbox Code Playgroud)
到目前为止我发现的WAR是将第一个getter函数修改为
template <ObjectCategory Category, std::size_t Idx, typename = std::enable_if_t<Category == ObjectCategory::FIRST>, bool = 0 /* dummy param */>
auto& getObject()
{
return std::get<Idx>(firstObjects_);
}
Run Code Online (Sandbox Code Playgroud)
即在末尾添加 SFINAE 以及虚拟模板 arg 以允许重载。