交替类型的参数包

9

我有一个结构体 C,其中包含多个结构体 A结构体 B的实例。例如:

struct A
{};

struct B
{};

struct C
{
    C(A&& o1, B&& p1, A&& o2)
    {}
    C(A&& o1, B&& p1, A&& o2, B&& p2, A&& o3)
    {}
    C(A&& o1, B&& p1, A&& o2, B&& p2, A&& o3, B&& p3, A&& o4)
    {}
    C(A&& o1, B&& p1, A&& o2, B&& p2, A&& o3, B&& p3, A&& o4, B&&p4, A&& o5)
    {}
};

因此,我不想提供具有不同参数数量的多个构造函数,我希望找到一些通用的解决方案。但是,构造函数参数的数量总是增加两个参数:B&&A&&。是否可以使用参数包来完成这个目标?或者是否有另一种解决方案,而无需为每个参数数量实现相应的构造函数?

目标是使struct C像以下示例一样构建:

C c1 = { A(), B(), A() };
C c2 = { A(), B(), A(), B(), A(), B(), A() };

etc.


1
编译时需要知道参数的数量吗?无论如何,考虑传递std::pair<A, B>&& - user10605163
在编译时会是锦上添花的一笔。然而,使用std::pair的方法存在一些问题(我尝试过):表达式看起来有点奇怪 C c = { A(), { B(), A() } }; 到目前为止,我还无法使用可变数量的参数创建类型安全的东西。 - PeWe
实际上,我认为这种初始化语法更好,因为它清楚地显示了哪两个参数是一组的。你所说的“类型安全”是什么意思?如果需要其他重载构造函数,您可以使用static_assert或SFINAE确保模板参数符合预期。 - user10605163
实际上,如果花括号不在中间,语法会更清晰。我对SFINAE不是很熟悉,听起来我应该学习更多关于它的知识。 - PeWe
3个回答

6
您可以使用可变参数模板和SFINAE,仅启用类型参数满足您(或任意)条件的构造函数。
#include <type_traits>
struct A {};
struct B {};

您需要使用 type_traits 来处理 std::false_typestd::true_type

alternates 模板是关键。它的目标是仅当 T1、...Tn 列表交替出现 XY 时,alternates<X, Y, T1, T2, T3, ..., Tn> 才从 std::true_type 继承。默认选择(如下)为否,但我们会为匹配的情况特化。

template <typename X, typename Y, typename... Ts>
struct alternates : std::false_type {};

我选择将这个模板更加通用,允许alternates<X, Y>true_type继承。空列表满足数学要求,即其中所有元素交替。这将是下面递归定义的一个好的权宜之计。

template <typename X, typename Y>
struct alternates<X, Y> : std::true_type {};

如果且仅当Ts...中第一个元素不在列表中时,Ts...减去第一个元素会与YXY首先!)交替出现。alternates<X, Y, Ts...>的任何其他列表也是如此。

template <typename X, typename Y, typename... Ts>
struct alternates<X, Y, X, Ts...>
: alternates<Y, X, Ts...> {};

struct C
{

我们将构造函数定义为一个模板,它首先接受一个参数包(类型将被推导,调用时不需要指定),并且具有用于SFINAE目的的默认模板参数。如果无法基于参数包计算出默认参数,则构造函数将不存在。我从示例中添加了关于键值对数量的额外条件,这些条件是我假设的。
    template<typename... Ts,
        typename = typename std::enable_if<
            sizeof...(Ts) % 2 == 1 &&
            sizeof...(Ts) >= 3 && // did you imply this?
            alternates<A, B, Ts...>::value
        >::type>
    C(Ts&&...);
};
SFINAE的工作方式是,只有当condition为真时,std::enable_if<condition, T>::type(即::type部分)才会被定义。这可以是任意可在编译时计算的布尔表达式。如果它为假,则在结尾处使用::type将导致替换失败,并且您尝试使用它的重载(例如C{A(), A(), A()})将不会被定义。
您可以测试下面的示例是否按预期工作。注释掉的那些不应该工作。
int main() {
    C c1 { A(), B(), A() };
    C c2 { A(), B(), A(), B(), A(), B(), A() };
    // C c3 {};                     // I assumed you need at least 2
    // C c4 { A(), B(), A(), A() }; // A, A doesn't alternate
    // C c5 { B(), A(), B() };      // B, A, B not allowed
    // C c6 { A(), B(), A(), B() }; // A, B, A, B doesn't pair
}

Try the code here.


1
我喜欢这个答案。你可以改进的一件事是你不需要使用std::conditional: template <typename X, typename Y, typename... Ts> struct alternates<X, Y, X, Ts...> : alternates<Y, X, Ts...> {}; - AndyG
我迫不及待地期盼着编译时反射的到来,这样我们就可以像这样编写类型检查而无需使用那些晦涩难懂的部分特化技术。 - Brian Bi
@Brian 如果我付出努力,是否也会得到更好的错误信息?特别是像 C c = {} 这样的错误信息:could not convert '<brace-enclosed initializer list>()' from '<brace-enclosed initializer list>' to 'C',让我很不满意。 - palotasb
@palotasb:你可以编写另一个具有!enable_if的构造函数,然后在内部失败static_assert - AndyG
你可以使用 template <class, class, class... Ts> struct alternates : std::integral_constant<bool, !sizeof...(Ts)> {}; 来删除一个特化。同时考虑是否隐式可转换不足够好。 - Deduplicator
@AndyG 谢谢,好主意! @Deduplicator 我更喜欢“alternates”的数学纯粹性,这意味着包交替使用前两个参数——正如名称所示。即使代码总行数有点多,要求至少有1(...或2?3?)个元素也是一个单独的问题。 - palotasb

5

我想您可以使用一个委派构造函数的模板

具体如下:

#include <utility>

struct A {};
struct B {};

struct C
 {
   C (A &&)
    { }

   template <typename ... Ts>
   C (A &&, B &&, Ts && ... ts) : C(std::forward<Ts>(ts)...)
    { }
 };

int main()
 {
   C(A{});
   C(A{}, B{}, A{});
   C(A{}, B{}, A{}, B{}, A{});
   C(A{}, B{}, A{}, B{}, A{}, B{}, A{});
 }

如果您需要至少三个元素(因此不是C(A {}),而是至少C(A {},B {},A {})),则非模板构造函数将变为:

   C (A &&, B &&, A&&)
    { }

0
也许这样做会有所帮助...
#include <iostream>
#include <vector>
#include <utility>

// Simple classes A & B to represent your alternating pattern classes.
class A{ public: int a; };
class B{ public: int b; };

// helper class template to act as a single parameter kind of like std::pair...
template<typename T, typename U>
class Pack{
private:
    T t_;
    U u_;

public:
    Pack( T&& t, U&& u ) : 
      t_( std::move( t ) ),
      u_( std::move( u ) )
    {}

    T getT() const { return t_; }
    U getU() const { return u_; }        
};

// your class with varying amount of parameters for its ctors
template<class T, class U>
class  C{
private:
    std::vector<Pack<T,U>> packs_;

public:
    template<typename... Packs>
    C( Packs&&... packs  ) : packs_{ std::move( packs )... } { }

    std::vector<Pack<T,U>> getPacks() const {
        return packs_;
    }
};

// A few overloaded ostream operator<<()s for easy printing...
std::ostream& operator<<( std::ostream& os, const A& a ) {
    os << a.a;
    return os;
}

std::ostream& operator<<( std::ostream& os, const B& b ) {
    os << b.b;
    return os;
}

template<typename T, typename U>
std::ostream& operator<<( std::ostream& os, const Pack<T,U>& pack ) {
    os << pack.getT() << " " << pack.getU() << '\n';
    return os;
}

// Main program to demonstrate its use
int main() {    
    Pack<int,double> p1( 1, 2.3 ), p2( 4, 9.2 ), p3( 5, 3.5 );        
    C<int, double> c( p1, p2, p3 );    
    for (auto& p : c.getPacks() )
        std::cout << p;

    std::cout << '\n'; 

    Pack<float, char> p4( 3.14f, 'a' ), p5( 6.95f, 'b' ),
                      p6( 2.81f, 'c' ), p7( 8.22f, 'd' );
    C<float, char> c2( p4, p5, p6, p7 );
    for ( auto& p : c2.getPacks() )
        std::cout << p;    

    return 0;
}

工作代码

-输出-

1 2.3
4 9.2
5 3.5

3.14 a
6.95 b
2.81 c
8.22 d

注意:我没有考虑任何奇数个参数的情况。如果您需要处理奇数个参数的更详细解决方案,可以参考其他答案中使用SFINAE委托构造函数的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接