C ++:如何使用大模板参数阻止编译器发送垃圾邮件错误?

时间:2014-01-06 22:26:03

标签: c++ compiler-errors g++

在我的C ++代码中,它主要依赖于模板元编程,我有类似的东西:

template <int TFoo, int TBar, int TBaz, int TQux, typename TSpam>
struct MyClassConfig {
    static int const Foo = TFoo;
    static int const Bar = TBar;
    static int const Baz = TBaz;
    static int const Qux = TQux;
    using Spam = TSpam;
};

template <typename Config>
class MyClass {
    ...
};

也就是说,我使用虚拟类来包含各种参数。这里通常有很多嵌套,所以MyClassConfig::Spam可能再次成为这样的配置类。

这一切都有效,直到编译器(g ++)决定它不喜欢我的代码。在这一点上,它会很高兴地打印出整个MyClassConfig。结合使用的其他形式的元编程,错误消息然后爆炸成兆字节。

编辑为了更清楚地了解发生了什么,请参阅my actual codea typical error output。第一个链接指向我的巨型配置类型别名为PrinterParams,然后将其作为模板参数提供给PrinterMain<>。看看错误输出是如何由这种配置类型组成的99%完全被吹灭。我相信如果只有PrinterParams以别名形式保存而不是由编译器扩展,那么错误将更具可读性。

3 个答案:

答案 0 :(得分:3)

使用您可以获得的最新版本的GCC。海湾合作委员会的人员改进了他们的错误信息。特别是4.8格式化它们更好,未来4.9添加颜色。回顾过去,我想知道如何使用GCC 4.4或更早版本。

如果您可以切换到clang,则会有优秀的错误消息。 Clang是GCC改进错误信息的主要推动力。

但在你的情况下,我认为罗伯特哈维是对的。您的示例看起来像模板滥用。

答案 1 :(得分:3)

感谢您致电C ++标准委员会。由于通话量非常大,我们在接听电话时遇到的延迟时间超过正常水平。对于给您带来的不便,我们深表歉意。您的来电对我们很重要。请留在线上。

(滑稽的帽子)

爆炸性错误消息问题过去是,现在仍然是C ++库编写者和用户的主要祸根。没有令人满意的解决方案。 Concepts被认为是C ++ 11中的解决方案,但最后一分钟就刮掉了概念。 Concepts Lite可能会也可能不会进入C ++ 1y。截至目前(C + 11),图书馆作者留下static_assert,但这需要手工考虑周到。体力劳动,嘘!要使用static_assert,您,图书馆作家,请执行以下操作:

  1. 记下模板参数必须满足的所有要求。
  2. 对于每个要求,创建一个布尔值编译时函数(constexpr或普通旧type<arg>::value),当满足要求时,该函数为真。
  3. 在用户可实例化的每个模板中的static_assert声明中使用所有这些函数。
  4. 虔诚地应用这些程序,您可能有可能产生不会爆炸到兆字节的可读错误消息。

    下面是一个匆忙抛出的例子,说明如何做到这一点。用g ++编译它,看看会发生什么。只记得它还没有准备好生产;)没有尝试处理引用,右值引用和const。请注意,clang ++报告static_assert 它在函数中找到的所有其他错误,这种错误会使其失败;而g ++仅报告static_assert个。因此对于clang ++,如果任何静态断言被触发,我们还需要压缩函数体;这是留给读者的练习。

    #include <vector>
    #include <algorithm>
    #include <type_traits>
    #include <utility>
    
    template <class T> struct supports_inequal
    {
        template <class U> static auto test(const U u) -> decltype(bool(u != u), char(0)) { }
        static std::array<char, 2> test(...) { }
        static const bool value = (sizeof(test(std::declval<T>())) == 1);
    };
    
    template <class T> struct supports_dereference
    {
        template <class U> static auto test(const U u) -> decltype(*u, char(0)) { }
        static std::array<char, 2> test(...) { }
        static const bool value = (sizeof(test(std::declval<T>())) == 1);
    };
    
    template <class T> struct supports_postincrement
    {
        template <class U> static auto test(U u) -> decltype(u++, char(0)) { }
        static std::array<char, 2> test(...) { }
        static const bool value = (sizeof(test(std::declval<T>())) == 1);
    };
    
    template <class T1, class T2> struct supports_assignment
    {
        template <class U, class V> static auto postincrement_test(U u, const V v) -> decltype(*u = *v, char(0)) { }
        static std::array<char, 2> test(...) { }
        static const bool value = (sizeof(test(std::declval<T1>(),std::declval<T2>())) == 1);
    };
    
    template <typename It1, typename It2, typename It3>
    void my_copy (It1 it1, It2 it2, It3 it3)
    {
        // Check that It1 and It2 are the same type
        static_assert (std::is_same<It1, It2>::value, "\n\n\nArgument 1 and argument 2 of my_copy(it1, it2, it3) must be of the same type\n\n\n");
        static const bool previous_assertions_1 = std::is_same<It1, It2>::value;
    
        static_assert (!previous_assertions_1 || supports_inequal<It1>::value, "\n\n\nArgument 1 and argument 2 of my_copy(it1, it2, it3) must be comparable with '!='\n\n\n");
        static const bool previous_assertions_2 = previous_assertions_1 && supports_inequal<It1>::value;
    
        static_assert (!previous_assertions_2 || supports_dereference<It1>::value, "\n\n\nArguments 1 and 2 of my_copy(it1, it2, it3) must be dereferenceable\n\n\n");
        static const bool previous_assertions_3 = previous_assertions_2 && supports_dereference<It1>::value;
    
        static_assert (!previous_assertions_3 || supports_dereference<It3>::value, "\n\n\nArgument 3 of my_copy(it1, it2, it3) must be dereferenceable\n\n\n");
        static const bool previous_assertions_4 = previous_assertions_3 && supports_dereference<It3>::value;
    
        static_assert (!previous_assertions_4 || supports_postincrement<It1>::value, "\n\n\nArguments 1 and 2 of my_copy(it1, it2, it3) must be postincrementable\n\n\n");
        static const bool previous_assertions_5 = previous_assertions_4 && supports_postincrement<It1>::value;
    
        static_assert (!previous_assertions_5 || supports_dereference<It3>::value, "\n\n\nArgument 3 and of my_copy(it1, it2, it3) must be postincrementable\n\n\n");
        static const bool previous_assertions_6 = previous_assertions_4 && supports_postincrement<It3>::value;
    
        std::copy (it1, it2, it3); // g++ does not complain here when static_assert fires, clang++ does (QoI issue": we have staic assert so that we could control the error messages!)
    }
    
    struct A {};
    
    int main ()
    {
        int *a, *b, *c;
        my_copy(a, b, c); // no error
        std::copy(a, b, c); // no error
        my_copy(a, b, A()); // human readable error message: argument 3 of my_copy(it1, it2, it3) must be dereferenceable
        std::copy(a, b, A()); // stack of incomprehensible error messages : "\n\n\nerror: no type named ‘value_type’ in ‘struct std::iterator_traits<A>’"?
    }
    

答案 2 :(得分:0)

更简单的解决方案是使用元函数:

template <int TFoo, int TBar, int TBaz, int TQux, typename TSpam>
struct MyClassConfig { };

template <int TFoo, int TBar, int TBaz, int TQux, typename TSpam>
constexpr int FooOf(MyClassConfig<TFoo, TBar, TBaz, TQux, TSpam>) {
    return TFoo;
};


using currentConfig = MyClassConfig<1,2,3,4, float>;
const int currentFoo = FooOf(currentConfig()); // = 1

由于FooOf不是MyClassConfig的成员,因此该类保持较小。