部分专业化,后跟可变参数模板参数

时间:2019-02-16 17:13:40

标签: c++ gcc variadic-templates template-deduction

我有一个涉及可变参数模板参数的部分专业化问题。带前缀的专业化

template<typename A, typename ... B>
struct Foo<A, B...> { };

工作正常,但是当我尝试与后缀匹配

template<typename A, typename ... B>
struct Foo<B..., A> { };

它不起作用。是否有一些我不知道的规则,或者是编译器问题? (我使用的是G ++ 7.4,尤其是cygwin的x86_64-w64-mingw32-g ++)

自包含示例来演示我的问题:

#include <iostream>

template<char ... C>
struct Str
{
    static constexpr char Value[] = { C..., '\0' };
};

template<char ... C>
constexpr char Str<C...>::Value[];

template<typename>
struct TrimFront;

template<char A, char ... C>
struct TrimFront<Str<A, C...>>
{
    typedef Str<C...> Type;
};

template<typename>
struct TrimBack;

template<char A, char ... C>
struct TrimBack<Str<C..., A>>
{
    typedef Str<C...> Type;
};

int main(int, char **)
{
    typedef Str<'a', 'b', 'c', 'd', 'e', 'f'> str_t;
    std::cout << str_t::Value << std::endl; // prints "abcdef"
    std::cout << TrimFront<str_t>::Type::Value << std::endl; // prints "bcdef"
    std::cout << TrimBack<str_t>::Type::Value << std::endl; // ERROR (incomplete type)
    return 0;
}

3 个答案:

答案 0 :(得分:1)

我想是

template<char A, char ... C>
struct TrimBack<Str<C..., A>>
{
    typedef Str<C...> Type;
};

由于可变参数包(A)不在最后位置,因此无法推论(“ C...和” C...“)。

OP合理地寻求参考

  

真的吗?非常不幸。您能否偶然指出标准中的规定?我似乎找不到相关的部分

我不是语言层,但在我看来,相关部分(C ++ 11标准)是14.8.2.5(“从类型推导模板参数”,“ [temp.deduct.type]”),要点9(重点是我的)

  

如果P的格式包含<T><i>,则将各个模板参数列表P_i的每个参数P与相应的模板参数进行比较。 A_i的相应模板参数列表的参数A如果P的模板参数列表包含的包扩展名不是最后一个模板参数,则整个模板参数列表都是非推论上下文。如果P_i是一个包扩展,则将P_i的模式与A的模板参数列表中的每个剩余参数进行比较。每次比较都会为模板参数包中以P_i展开的后续位置推导模板参数。

因此,如果我没记错,TrimBack<str_t>(又称TrimBack<Str<'a', 'b', 'c', 'd', 'e', 'f'>>)会报错,因为

1)在第一阶段,Str<C..., A>匹配Str<'a', 'b', 'c', 'd', 'e', 'f'>

2),但在第二阶段,尝试推导C...A类型,P(在此阶段为Str<C..., A>)“包含一个包扩展那不是最后一个模板参数”,因此“整个模板参数列表是一个非推论上下文”。

答案 1 :(得分:1)

像这样的局部类模板专门化

template<typename> struct TrimBack;
template<char ...C, char A> struct TrimBack<Str<C..., A>> {}
不允许使用

,因为推导C...A时,将执行deduction from a type,并且最后一个pack参数将其设为non-deduced context

您可以做的是使用帮助程序类型“解包”包装,然后“重新包装”,减去最后一个元素。

template <char ...P>
struct dummy {};

template <class T, char ...P>
struct internal;

template <char ...P1, char T, char ...P2>
struct internal<dummy<P1...>, T, P2...>
{
    using type = typename internal<dummy<P1..., T>, P2...>::type; // unwrap one recursively
};

template <char ...P1, char T>
struct internal<dummy<P1...>, T>
{
    using type = Str<P1...>; // re-wrap all but the last one
};

template <typename>
struct TrimBack;

template <char ...C>
struct TrimBack<Str<C...>>
{
    using Type = typename internal<dummy<>, C...>::type;
};

现在这应该可行:

std::cout << TrimBack<str_t>::Type::Value << std::endl;  // prints "abcde"

Live demo

答案 2 :(得分:0)

这是使用boost::mp11的解决方案:

内联评论:

#include <iostream>
#include <boost/mp11.hpp>

template<char ... C>
struct Str
{
    static constexpr char Value[] = { C..., '\0' };
};

template<char ... C>
constexpr char Str<C...>::Value[];

template<typename>
struct TrimFront;

template<char A, char ... C>
struct TrimFront<Str<A, C...>>
{
    typedef Str<C...> Type;
};

template<typename>
struct TrimBack;



using namespace boost::mp11;

// a means of turning chars into types
template<char c> struct c_char {
    constexpr char value() { return c; }
 };

// a means of turning an mp_list of c_char<char>... back into a Str<char...>
 template<typename>
 struct back_to_Str;

 template<char...cs>
 struct back_to_Str<mp_list<c_char<cs>...>>
 {
     using result = Str<cs...>;
 };

// TrimBack using types as computation steps:
template<char... C>
struct TrimBack<Str<C...>>
{ 
    // turn the input chars into an mp_list of c_char
    // always use types, they're much easier than values when metaprogramming
    using input = mp_list<c_char<C>...>;        

    // reverse the list
    using reversed = mp_reverse<input>;

    // pop the front c_char<>
    using popped = mp_pop_front<reversed>;

    // reverse again
    using re_reversed = mp_reverse<popped>;

    // turn back into a Str<char...>
    using Type = typename back_to_Str<re_reversed>::result;
};

int main(int, char **)
{
    typedef Str<'a', 'b', 'c', 'd', 'e', 'f'> str_t;
    std::cout << str_t::Value << std::endl; // prints "abcdef"
    std::cout << TrimFront<str_t>::Type::Value << std::endl; // prints "bcdef"
    std::cout << TrimBack<str_t>::Type::Value << std::endl; // prints "abcde"
    return 0;
}

预期输出:

abcdef
bcdef
abcde

http://coliru.stacked-crooked.com/a/387e5dc7ef262f1f

有了我们新发现的知识,我们可以简化:

#include <iostream>
#include <boost/mp11.hpp>

using namespace boost::mp11;

template<char c> 
struct c_char {
    static constexpr char value() { return c; }
};

template<typename...> struct Str;

template<char... C>
struct Str<c_char<C>...>
{
    static constexpr auto size() -> std::size_t { return sizeof...(C) + 1; }
    static constexpr char Value [size()] = { C..., '\0' };
};

template<char...C> using make_Str = Str<c_char<C>...>;

template<typename List>
struct TrimFront
{
    using Type = mp_pop_front<List>;
};

template<typename List>
struct TrimBack
{ 
    using Type = mp_reverse<mp_pop_front<mp_reverse<List>>>;
};

int main(int, char **)
{
    using str_t = make_Str<'a', 'b', 'c', 'd', 'e', 'f'>;
    std::cout << str_t::Value << std::endl; // prints "abcdef"
    std::cout << TrimFront<str_t>::Type::Value << std::endl; // prints "bcdef"
    std::cout << TrimBack<str_t>::Type::Value << std::endl; // prints "abcde"
    return 0;
}