了解 SFINAE:部分专用类之外的成员函数声明,以及可变参数类中的模板

Understanding SFINAE : declarations of member functions outside of partially specialized classes, and templates within variadic classes

提问人:Irreducible Polynomial 提问时间:11/12/2023 最后编辑:Irreducible Polynomial 更新时间:11/13/2023 访问量:112

问:

我想做什么:

创建一个“InstructionSet”对象,该对象

  • 通常(无需指定类型)由其他位置的容器(指针)持有
  • 保存可变数量的指令对象(可能具有重复类型),其类型满足概念“is_instruction_type”
  • 具有“extract”函数,可生成一个新的“InstructionSet”,其中包含单个特定类型的所有指令

我试图通过在可变参数类上使用继承来实现这一点,这样就可以使用递归来存储正确类型的每条指令;然后,指向类型“InstructionSet<>”的指针可以使用一个虚拟函数,以便能够访问继承的底部,并在对象上重复。

//Define recursively
template<typename...Args> requires (is_instruction_type<Args> && ...)
class InstructionSet;


//Base
template<>
class InstructionSet<> 
{
public:
    InstructionSet() {};
    virtual ~InstructionSet() {};

    template<typename U>
    auto extract()
    {
        return extractHelper()->extract();
    };

protected:
    virtual InstructionSet<>* extractHelper();
};

//Recur
template<typename T, typename ...Rest> requires is_instruction_type<T>
class InstructionSet<T, Rest...> : public InstructionSet<Rest...>
{
public:
    InstructionSet(T&& t, Rest&&...rest);
    InstructionSet(T&& t, InstructionSet<Rest...>&& set);

    virtual ~InstructionSet() {};

    template<typename U> requires std::same_as<T, U>
    auto extract();

    template<typename U> requires !std::same_as<T, U>
    auto extract();


    virtual InstructionSet<T, Rest...>* extractHelper()
    {
        return this;
    };

private:
    T _instruction;

};



template<typename T, typename ...Rest> requires is_instruction_type<T>
inline InstructionSet<T, Rest...>::InstructionSet(T&& t, Rest&& ...rest) : InstructionSet<Rest...>(std::forward<Rest>(rest)...),
_instruction(std::forward<T>(t))
{

}
template<typename T, typename ...Rest> requires is_instruction_type<T>
inline InstructionSet<T, Rest...>::InstructionSet(T&& t, InstructionSet<Rest...>&& set)
{
    _instruction = std::forward<T>(t);
    std::construct_at((InstructionSet<Rest...>*)this, std::move(set));
}


template<typename T, typename ...Rest> requires is_instruction_type<T>
template<typename U> requires std::same_as<T, U>
auto InstructionSet<T, Rest...>::extract<U>()
{
    return InstructionSet(_instruction, std::move(((InstructionSet<Rest...>*)this)->extract()));
}

template<typename T, typename ...Rest> requires is_instruction_type<T>
template<typename U> requires (!std::same_as<T, U>)
auto InstructionSet<T, Rest...>::extract<U>()
{
    return ((InstructionSet<Rest...>*)this)->extract();
}

我有两个问题:

  • 在多个模板下,应该如何在类之外定义“extract”?
  • 在部分专用类定义中应如何处理类成员?(是否也应该有一个完整的类定义?

我尝试了包含/排除尖括号的不同组合,并尝试将函数定义移动到类内部,但都抛出了错误。

我认为我了解具有类型定义和静态变量的类的 SFINAE 基础知识,但是对于更复杂的类,我感到困惑,并且似乎找不到很多示例。

C++ variadic-templates sfinae

评论

1赞 user12002570 11/12/2023
C++是一种非常复杂的语言,尝试一次解决/问一个问题。
0赞 user12002570 11/12/2023
“我认为我了解具有类型定义和静态变量的类的 SFINAE 基础知识”另外,不要忘记Stackoverflow不是“C++模板简介”。市面上有很多好的 c++ 书籍。此外,我很确定类模板不支持 SFINAE,而函数模板支持 SFINAE。
0赞 Irreducible Polynomial 11/12/2023
@user12002570 en.cppreference.com/w/cpp/language/sfinae,部分专业化部分中的 SFINAE 是否不是类/结构模板 SFINAE 支持?(或者我没有使用正确的词来描述它?
1赞 Irreducible Polynomial 11/12/2023
另外,感谢您的建议和书单

答:

0赞 Ted Lyngmo 11/12/2023 #1

我认为您可以通过不进行专业化并使用 a 来存储指令来简化它。std::tuple

首先,一个辅助函数,用于创建一个参数包中的所有索引,其中类型为:std::index_sequenceT

// get an index_sequence for where all T's are in Ts...
template <class T, class... Ts>
constexpr auto get_indices_for() {
    constexpr auto inds_sel = []<std::size_t... Is>(std::index_sequence<Is...>) {
        std::array<std::size_t, sizeof...(Ts)> indices{};

        std::size_t sel = 0;
        (..., (std::same_as<T, std::tuple_element_t<Is, std::tuple<Ts...>>> &&
               (indices[sel++] = Is)));

        return std::pair{indices, sel};
    }(std::make_index_sequence<sizeof...(Ts)>{});

    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
        return std::index_sequence<inds_sel.first[Is]...>{};
    }(std::make_index_sequence<inds_sel.second>{});
}

然后,如果将指令存储在 中,提取可能如下所示:std::tuple

struct InstructionSetBase {
    virtual ~InstructionSetBase() = default;
};

template <class... Ts>
    requires(is_instruction_type<Ts> && ...)
class InstructionSet : public InstructionSetBase {
public:
    template <class... Us>
    InstructionSet(Us&&... us) : _instructions{std::forward<Us>(us)...} {}

    template <std::size_t... Is>  // extract by indices
    InstructionSet<std::tuple_element_t<Is, std::tuple<Ts...>>...> extract();

    template <class U>  // extract all of a certain by type
    auto extract() {
        return [this]<std::size_t... Is>(std::index_sequence<Is...>) {
            return extract<Is...>();
        }(get_indices_for<U, Ts...>());
    }

private:
    std::tuple<Ts...> _instructions;
};

template <class... Ts>
InstructionSet(Ts&&...) -> InstructionSet<std::remove_cvref_t<Ts>...>;

template <class... Ts>
    requires(is_instruction_type<Ts> && ...)
template <std::size_t... Is>
InstructionSet<std::tuple_element_t<Is, std::tuple<Ts...>>...>
InstructionSet<Ts...>::extract() {
    return {std::get<Is>(_instructions)...};
}

演示

评论

0赞 Irreducible Polynomial 11/12/2023
我以前从未将 std::get 用于类型,但是,如果我正确理解定义,此实现期望每条指令只有一个?而我想存储多个相同类型的文件,并返回所有相同类型的文件。
0赞 Irreducible Polynomial 11/12/2023
另外,另一件事是我希望它能够通用地存储在其他地方的数据结构中(所以我试图使用虚拟方法使它能够从指向 InstructionSet 的指针<>获取整个对象)
0赞 Ted Lyngmo 11/12/2023
@IrreduciblePolynomial好的,我现在已经更新了它,以便可以提取所有特定类型。我还用析构函数制作了一个基类,这样你就可以将基类指针存储在容器等中。extractvirtual
0赞 Igor Tandetnik 11/12/2023 #2

我想出了这个:

#include <tuple>
#include <type_traits>

namespace internal {

// The number of occurrences of U in Ts
template <typename U, typename... Ts>
constexpr std::size_t CountSameType() {
    return (std::is_same_v<U, Ts> + ...);
}

// The index of Ith occurrence of U in {First, Rest...}
template <std::size_t I, typename U, typename First, typename ... Rest>
struct IthIndex : public std::integral_constant<std::size_t,
  1 + IthIndex<I - std::is_same_v<U, First>, U, Rest...>::value>
{};

template <typename U, typename ... Rest>
struct IthIndex<0, U, U, Rest...> : public std::integral_constant<std::size_t, 0> {};

// An index_sequence mapping occurrences of U in Ts,
// given a sequence {0, 1, CountSameType<U, Ts...>}
template <typename U, typename... Ts, std::size_t... Is>
std::index_sequence<IthIndex<Is, U, Ts...>::value...> 
    SameTypeIndexes(std::index_sequence<Is...>);

// Produce a tuple that's a subset of the given tuple,
// containing only elements at given indexes.
template <typename Tuple, std::size_t ... Is>
auto ExtractFromTuple(const Tuple& t, std::index_sequence<Is...>) {
    return std::make_tuple(std::get<Is>(t)...);
}

}  // namespace internal

template <typename... Ts>
class InstructionSet;

template <typename... Ts>
auto MakeInstructionSet(const std::tuple<Ts...>& t) { return InstructionSet<Ts...>(t); }

template <typename... Ts>
class InstructionSet {
    std::tuple<Ts...> instructions;
public:
  InstructionSet(Ts&&... args) : instructions(args...) {}
  InstructionSet(const std::tuple<Ts...>& t) : instructions(t) {}

  template <typename U>
  auto extract() {
     constexpr std::size_t N = internal::CountSameType<U, Ts...>();
     using SameTypeIndexes =
         decltype(internal::SameTypeIndexes<U, Ts...>(std::make_index_sequence<N>{}));
     return MakeInstructionSet(internal::ExtractFromTuple(instructions, SameTypeIndexes{}));
  }
};

演示