2016-04-30 71 views
1

我正在考虑以下问题:参数包扩展静态变量的

让我们在这样定义的合并数组合并功能:

// input is (const void*, size_t, const void*, size_t,...) 
template<typename...ARGS> 
MyArray Concatenation(ARGS...args) 

让我们有几个结构的静态性能

struct A { static void* DATA; static size_t SIZE; }; 
struct B { static void* DATA; static size_t SIZE; }; 
struct C { static void* DATA; static size_t SIZE; }; 

我想有一个方法:

template<typename...ARGS> 
MyArray AutoConcatenation(); 

其中ARGS应该与提到的静态接口结合在一起。 按照方法应该有同样的输出:

AutoConcatenation<A, B, C>(); 
Concatenation(A::DATA, A::SIZE, B::DATA, B::SIZE, C::DATA, C::SIZE); 

我的问题是如何实现参数包扩展。

我想:

// not working 
template<typename...ARGS> 
MyArray AutoConcatenation() 
{ 
    return Concatenation((ARGS::DATA, ARGS::SIZE)...); 
} 

怎么样扩张

ARGS::DATA... // Correct expansion of pointers 
ARGS::SIZE... // Correct expansion of sizes 
(ARGS::DATA, ARGS::SIZE)... // Seems to be expansion of sizes 

为顾问只是信息。我正在寻找执行AutoConcatenation方法,不是为了它的重新声明,也不是为了重新声明以前的代码,谢谢。

+0

你是什么意思*连接*? – Zereges

+0

没关系。解决方案与函数的语义无关。对于你的信息,如果DATA是c字符串,A :: DATA =“abc”,A :: SIZE = 3,则连接(A :: DATA,A :: SIZE,A :: DATA,A :: SIZE)会与数据“abcabc”和大小为6的阵列。 – user4663214

回答

1

懒惰的解决方案使用std::tuple

  • 使DATASIZE的参数组中的每个元素的元组,
  • 使用std::tuple_cat压扁元组到一个大的元组列表,
  • 申请通过展开std::index_sequence中的索引列表将结果元组的元素设置为Concatenation

在下面的代码,测试工具是比实际的解决方案更长:

#include <cstddef> 
#include <tuple> 
#include <utility> 
#include <iostream> 
#include <typeinfo> 
#include <type_traits> 

struct MyArray { }; 
template<class... ARGS> MyArray Concatenation(ARGS... args) 
{ 
    // Just some dummy code for testing. 
    using arr = int[]; 
    (void)arr{(std::cout << typeid(args).name() << ' ' << args << '\n' , 0)...}; 
    return {}; 
} 

struct A { static void* DATA; static std::size_t SIZE; }; 
struct B { static void* DATA; static std::size_t SIZE; }; 
struct C { static void* DATA; static std::size_t SIZE; }; 

// Also needed for testing. 
void* A::DATA; 
std::size_t A::SIZE; 
void* B::DATA; 
std::size_t B::SIZE; 
void* C::DATA; 
std::size_t C::SIZE; 


// The useful stuff starts here. 

template<class T, std::size_t... Is> MyArray concat_hlp_2(const T& tup, std::index_sequence<Is...>) 
{ 
    return Concatenation(std::get<Is>(tup)...); 
} 

template<class T> MyArray concat_hlp_1(const T& tup) 
{ 
    return concat_hlp_2(tup, std::make_index_sequence<std::tuple_size<T>::value>{}); 
} 

template<class... ARGS> MyArray AutoConcatenation() 
{ 
    return concat_hlp_1(std::tuple_cat(std::make_tuple(ARGS::DATA, ARGS::SIZE)...)); 
} 


int main() 
{ 
    AutoConcatenation<A, B, C>(); 
} 

如果你想避免std::tuplestd::tuple_cat(可在编译时间上而言是沉重的) ,这里有一个使用数组索引的替代方法。

测试代码保持不变,这仅仅是多汁的东西:

template<std::size_t Size> const void* select(std::false_type, std::size_t idx, 
    const void* (& arr_data)[Size], std::size_t (&)[Size]) 
{ 
    return arr_data[idx]; 
} 

template<std::size_t Size> std::size_t select(std::true_type, std::size_t idx, 
    const void* (&)[Size], std::size_t (& arr_size)[Size]) 
{ 
    return arr_size[idx]; 
} 

template<std::size_t... Is> MyArray concat_hlp(std::index_sequence<Is...>, 
    const void* (&& arr_data)[sizeof...(Is)/2], std::size_t (&& arr_size)[sizeof...(Is)/2]) 
{ 
    return Concatenation(select(std::bool_constant<Is % 2>{}, Is/2, arr_data, arr_size)...); 
} 

template<class... ARGS> MyArray AutoConcatenation() 
{ 
    return concat_hlp(std::make_index_sequence<sizeof...(ARGS) * 2>{}, {ARGS::DATA...}, {ARGS::SIZE...}); 
} 

再次索引的顺序原来的两倍参数包的大小,但我们建立的DATASIZE,然后单独的阵列使用标签调度来根据当前索引的奇偶性从一个或另一个中选择元素。

这看起来可能不像前面的代码那么好,但它不涉及任何模板递归(就我所知,在现代编译器中使用编译器内在函数实现了std::make_index_sequence),并减少了模板实例化的数量,所以编译应该更快。

select帮助器可以通过使用指向静态成员的指针数组来制作constexpr,但实际上这是不必要的。我已经测试过MSVC 2015 U2,Clang 3.8.0和GCC 6.1.0,他们都将此优化为直接调用Concatenation(就像基于元组的解决方案一样)。

+0

不错。谢谢。 – user4663214

0

以下是可能的解决方案:

enum Delimiters { Delimiter }; 
const void* findData(size_t count) { return nullptr; } 

template<typename...ARGS> 
const void* findData(size_t count, size_t, ARGS...args) 
{ 
    return findData(count, args...); 
} 

template<typename...ARGS> 
const void* findData(size_t count, const void* data, ARGS...args) 
{ 
    return count ? findData(count - 1, args...) : data; 
} 

template<typename...ARGS> 
MyArray reordered(size_t count, Delimiters, ARGS...args) 
{ 
    return Concatenate(args...); 
} 

template<typename...ARGS> 
MyArray reordered(size_t count, const void* size, ARGS...args) 
{ 
    return reordered(count, args...); 
} 

template<typename...ARGS> 
MyArray reordered(size_t count, size_t size, ARGS...args) 
{ 
    return reordered(count + 1, args..., findData(count, args...), size); 
} 

template<typename...ARGS> 
MyArray AutoConcatenate() 
{ 
    return reordered(0, ARGS::LAYOUT_SIZE..., ARGS::LAYOUT..., Delimiter); 
} 

如果你知道更优雅的方式,请让我知道。

编辑

一个小更优雅的方式是保持函数参数计数作为模板参数...

+0

一般来说,回答您自己的问题不是习惯做法。也许你应该把它添加到你原来的文章中,然后改变你的文章以寻求一个更优雅的解决方案。 – 2016-05-01 00:36:07

+0

@William问自己的问题没有错。 – Zereges

+0

@Zerges,当然。那么回答它呢?特别是甚至没有给它一天?此外,我不记得称它是“错误的”。 – 2016-05-01 16:26:11

0

我认为下面是更优雅,它说明了常见的递归拆包模式。最后,它不会执行任何有关内存布局的巫术,并且尽可能地习惯C++泛型编程。

#include <iostream> 
#include <string> 
using namespace std; 

// Handle zero arguments. 
template <typename T = string> 
T concat_helper() { return T(); } 

// Handle one pair. 
template <typename T = string> 
T concat_helper(const T &first, size_t flen) { return first; } 

// Handle two or more pairs. Demonstrates the recursive unpacking pattern 
// (very common with variadic arguments). 
template <typename T = string, typename ...ARGS> 
T concat_helper(const T &first, size_t flen, 
       const T &second, size_t slen, 
       ARGS ... rest) { 
    // Your concatenation code goes here. We're assuming we're 
    // working with std::string, or anything that has method length() and 
    // substr(), with obvious behavior, and supports the + operator. 
    T concatenated = first.substr(0, flen) + second.substr(0, slen); 
    return concat_helper<T>(concatenated, concatenated.length(), rest...); 
} 

template <typename T, typename ...ARGS> 
T Concatenate(ARGS...args) { return concat_helper<T>(args...); } 

template <typename T> 
struct pack { 
    T data; 
    size_t dlen; 
}; 

template <typename T> 
T AutoConcatenate_helper() { return T(); } 

template <typename T> 
T AutoConcatenate_helper(const pack<T> *packet) { 
    return packet->data; 
} 

template <typename T, typename ...ARGS> 
T AutoConcatenate_helper(const pack<T> *first, const pack<T> *second, 
         ARGS...rest) { 
    T concatenated = Concatenate<T>(first->data, first->dlen, 
            second->data, second->dlen); 
    pack<T> newPack; 
    newPack.data = concatenated; 
    newPack.dlen = concatenated.length(); 

    return AutoConcatenate_helper<T>(&newPack, rest...); 
} 

template <typename T, typename ...ARGS> 
T AutoConcatenate(ARGS...args) { 
    return AutoConcatenate_helper<T>(args...); 
} 

int main() { 
    pack<string> first; 
    pack<string> second; 
    pack<string> third; 
    pack<string> last; 

    first.data = "Hello"; 
    first.dlen = first.data.length(); 

    second.data = ", "; 
    second.dlen = second.data.length(); 

    third.data = "World"; 
    third.dlen = third.data.length(); 

    last.data = "!"; 
    last.dlen = last.data.length(); 

    cout << AutoConcatenate<string>(&first, &second, &third, &last) << endl; 

    return 0; 
} 

我们既不改变Concatenate<>()声明,也不是说的AutoConcatenate<>(),按要求。和我们一样,我们可以自由执行AutoConcatenate<>(),并且我们假设有一些实现Concatenate<>()(我们为一个工作示例提供了一个简单的实现)。

+0

你确实改变了AutoConcatenation的声明:'template MyArray AutoConcatenation();',没有输入作为参数...我不认为你的解决方案更优雅,因为增量连接,这对我来说分配太多了。我应该提到,'Concatenation(...)'方法只执行一次分配,不管参数是否计数...我的意思是重新排序输入参数或以某种方式以上述方式扩展它们...... – user4663214

+0

好,但是修改是显而易见的(就宣言而言)。至于你的其他问题:好吧,那不是OP的一部分。无论如何,这些也可以通过简单的修改来修复。就像我说的,我为一个工作示例提供了一个简单的实现。你以任何你想要的方式实现它,并且你应该在Concatenate()的实现中关注分配问题......如果你不能,那么这是糟糕的设计。 – 2016-05-02 12:31:13