SFINAE:函数模板优化
SFINAE: function templates optimation
我想用成员函数模板以某种方式迭代一个元组(以便稍后从给定的模板类型 T
创建一个新类型的元组)。
但是,没有使用中断条件(函数)所以我得到这个错误:
invalid use of incomplete type: 'class std::tuple_element<0ul, std::tuple<> >'
问题似乎是,即使 N == size
的元组,std::tuple_element_t
被评估为 N != size
而不是作为 SFINAE 处理。
两个示例都显示了不同的无效解决方案。我做错了什么?
注意:为了最小化示例,省略了用 is_same
求值的函数。
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0, typename std::enable_if_t<N == size>* = nullptr>
int get()
{
return 0;
}
template<typename T, std::size_t N = 0, typename std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0>
std::enable_if_t<N == size, int> get()
{
return 0;
}
template<typename T, std::size_t N = 0>
std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int> get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
一种解决方法是使用第三个函数来计算 sizeof tuple - 2,然后计算 sizeof tuple - 1,但这真的有必要吗?
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0, typename std::enable_if_t<(N == size - 1) && std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return 1;
}
template<typename T, std::size_t N = 0, typename std::enable_if_t<(N == size - 1) && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return 2;
}
template<typename T, std::size_t N = 0, typename std::enable_if_t<(N < size - 1) && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
使用一个额外的结构怎么样,通过部分特化,可以避免使用 std::tuple_element_t
?
我的意思是,类似
template <typename T, std::size_t N>
struct checkType
{ constexpr static bool value
= std::is_same<T, std::tuple_element_t<N, tuple>>::value; };
template <typename T>
struct checkType<T, size>
{ constexpr static bool value = false; };
template <typename, std::size_t N = 0>
std::enable_if_t<N == size, int> get ()
{ return 0; }
template <typename T, std::size_t N = 0>
std::enable_if_t<(N < size) && ! checkType<T, N>::value, int> get()
{ return get<T, N + 1>() - 1; }
正如@PiotrSkotnicki 在对问题的评论中所建议的那样,这是您修复后的第二个示例:
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0>
std::enable_if_t<N == size-1, int>
get()
{
return std::is_same<T, std::tuple_element_t<N, tuple>>::value ? N : 0;
}
template<typename T, std::size_t N = 0>
std::enable_if_t<N != size-1 && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int>
get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
问题是什么?
考虑以下行:
std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int> get()
在这种情况下,N
被替换以评估 enable_if
的条件,即使在 N == size
时也是如此(必须替换才能发现 N == size
确实).
因此,tuple_element_t
(让我说)发出了一个超出范围的结果,这就是你得到编译错误的原因。
我只是更新了您的代码以避免在遍历 N
时达到 size
。这是一个使用 size-1
作为在函数之间切换的值的问题。
在对 的评论中回答 OP 说:
It does solve the problem but not for automatic type return type deduction based on which function is used (returning int was just an example). I should have been clearer on this.
它遵循一个最小的工作示例,可能也解决了这个问题。
在这种情况下,在继承和标签调度方面进行推理要容易得多,以减少由于 sfinae 引起的样板文件。此外,如果需要,可以使用特化来为特定类型引入特定行为。
最后一种情况,即不属于类型列表的类型的情况,也可以在专用函数中轻松处理。
它遵循代码:
#include <type_traits>
#include <tuple>
template<typename>
struct tag {};
template<typename...>
struct B;
template<typename T, typename... Ts>
struct B<T, Ts...>: B<Ts...> {
using B<Ts...>::get;
auto get(tag<T>) {
return T{};
}
};
template<>
struct B<> {
template<typename T>
auto get(tag<T>) {
return nullptr;
}
};
template<typename...Ts>
struct A: private B<Ts...>
{
template<typename T>
auto get() {
return B<Ts...>::get(tag<T>{});
}
};
int main()
{
A<int, float, double, float, float> a;
static_assert(std::is_same<decltype(a.get<char>()), std::nullptr_t>::value, "!");
static_assert(std::is_same<decltype(a.get<float>()), float>::value, "!");
}
我想用成员函数模板以某种方式迭代一个元组(以便稍后从给定的模板类型 T
创建一个新类型的元组)。
但是,没有使用中断条件(函数)所以我得到这个错误:
invalid use of incomplete type: 'class std::tuple_element<0ul, std::tuple<> >'
问题似乎是,即使 N == size
的元组,std::tuple_element_t
被评估为 N != size
而不是作为 SFINAE 处理。
两个示例都显示了不同的无效解决方案。我做错了什么?
注意:为了最小化示例,省略了用 is_same
求值的函数。
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0, typename std::enable_if_t<N == size>* = nullptr>
int get()
{
return 0;
}
template<typename T, std::size_t N = 0, typename std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0>
std::enable_if_t<N == size, int> get()
{
return 0;
}
template<typename T, std::size_t N = 0>
std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int> get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
一种解决方法是使用第三个函数来计算 sizeof tuple - 2,然后计算 sizeof tuple - 1,但这真的有必要吗?
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0, typename std::enable_if_t<(N == size - 1) && std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return 1;
}
template<typename T, std::size_t N = 0, typename std::enable_if_t<(N == size - 1) && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return 2;
}
template<typename T, std::size_t N = 0, typename std::enable_if_t<(N < size - 1) && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
使用一个额外的结构怎么样,通过部分特化,可以避免使用 std::tuple_element_t
?
我的意思是,类似
template <typename T, std::size_t N>
struct checkType
{ constexpr static bool value
= std::is_same<T, std::tuple_element_t<N, tuple>>::value; };
template <typename T>
struct checkType<T, size>
{ constexpr static bool value = false; };
template <typename, std::size_t N = 0>
std::enable_if_t<N == size, int> get ()
{ return 0; }
template <typename T, std::size_t N = 0>
std::enable_if_t<(N < size) && ! checkType<T, N>::value, int> get()
{ return get<T, N + 1>() - 1; }
正如@PiotrSkotnicki 在对问题的评论中所建议的那样,这是您修复后的第二个示例:
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0>
std::enable_if_t<N == size-1, int>
get()
{
return std::is_same<T, std::tuple_element_t<N, tuple>>::value ? N : 0;
}
template<typename T, std::size_t N = 0>
std::enable_if_t<N != size-1 && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int>
get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
问题是什么?
考虑以下行:
std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int> get()
在这种情况下,N
被替换以评估 enable_if
的条件,即使在 N == size
时也是如此(必须替换才能发现 N == size
确实).
因此,tuple_element_t
(让我说)发出了一个超出范围的结果,这就是你得到编译错误的原因。
我只是更新了您的代码以避免在遍历 N
时达到 size
。这是一个使用 size-1
作为在函数之间切换的值的问题。
在对
It does solve the problem but not for automatic type return type deduction based on which function is used (returning int was just an example). I should have been clearer on this.
它遵循一个最小的工作示例,可能也解决了这个问题。
在这种情况下,在继承和标签调度方面进行推理要容易得多,以减少由于 sfinae 引起的样板文件。此外,如果需要,可以使用特化来为特定类型引入特定行为。
最后一种情况,即不属于类型列表的类型的情况,也可以在专用函数中轻松处理。
它遵循代码:
#include <type_traits>
#include <tuple>
template<typename>
struct tag {};
template<typename...>
struct B;
template<typename T, typename... Ts>
struct B<T, Ts...>: B<Ts...> {
using B<Ts...>::get;
auto get(tag<T>) {
return T{};
}
};
template<>
struct B<> {
template<typename T>
auto get(tag<T>) {
return nullptr;
}
};
template<typename...Ts>
struct A: private B<Ts...>
{
template<typename T>
auto get() {
return B<Ts...>::get(tag<T>{});
}
};
int main()
{
A<int, float, double, float, float> a;
static_assert(std::is_same<decltype(a.get<char>()), std::nullptr_t>::value, "!");
static_assert(std::is_same<decltype(a.get<float>()), float>::value, "!");
}