首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用宏辅助SFINAE模板元编程的可视化解析

使用宏辅助SFINAE模板元编程的可视化解析
EN

Code Review用户
提问于 2014-12-07 14:16:58
回答 3查看 823关注 0票数 4

我最近被介绍到SFINAE,以解决不必要的晋升优先问题。

也就是说,我希望用Foo::Foo(long)捕获整数类型,用Foo::Foo(double)捕获浮点类型,但是遗憾的是,int -> double而不是long

代码语言:javascript
复制
    /*
    Constructors for various types, for example, 'Object{"foo"}, Object{42}, Object{3.14} should create a String Long Float respectively

    There is a problem with constructors.

    Python supports a single integer type, which we wrap with the Long class
    And the single floatingpoint type, which we wrap with the Float class

    In an ideal world we would just provide two overloads to allow a generic 
    Object to be initialised as one of these types:

        Object(long   l) : Object{ Long {l} }  { }
        Object(double d) : Object{ Float{d} }  { }

    We want Object{5} to create a Long{5}
    Unfortunately, int gets promoted to double, not long ( http://stackoverflow.com/a/27276398/435129 )

    Very annoying. Fortunately we can use some SFINAE cunning.
    */
    #define DECAY(T) \
                            typename std::decay<T>::type

    #define IS_INTEGRAL(T) \
                            std::is_integral< DECAY(T) >::value

    #define IS_FLOATING(T) \
                            std::is_floating_point< DECAY(T) >::value

    #define SUBFAIL_UNLESS(PRED) \
                            typename X = typename std::enable_if<PRED>::type

    /*
    Note that the first template encounters a substitution failure for any non-integral type,
    hence only redirects integral types to init(long(t))

    Similarly for floatingpoint types.

    Note also the unfortunate use of an empty '...' C parameter expansion.
    Without this the compiler will complain that two templates are attempting 
    to wrap a function with the same signature.

    Unfortunately it isn't smart enough to know that the conditions are mutually exclusive.

    If you required trapping of three or more such special cases, see NOTE_3_CASES at the bottom of the file.
    Also http://ideone.com/oDOSLH
    */

public:
    template<typename T, SUBFAIL_UNLESS(IS_INTEGRAL(T)) > explicit Object( T&& t      ) { init(long  (t)); }
    template<typename T, SUBFAIL_UNLESS(IS_FLOATING(T)) > explicit Object( T&& t, ... ) { init(double(t)); }

private:
    void init(long);
    void init(double);

    #undef DECAY
    #undef IS_INTEGRAL
    #undef IS_FLOATING

我知道宏通常是不受欢迎的。但是,在这种情况下,它们提供了一个非常清晰的解决方案,使我的大脑很容易解析代码。

我想知道在这种情况下,使用宏是否合理,如果没有,那么最好的选择是什么。

EN

回答 3

Code Review用户

回答已采纳

发布于 2014-12-07 21:11:16

代码格式化规则#1 :编写可读的代码.

规则2:不要做别人告诉你不要做的事.直到你更清楚的知道。

针对宏的规则是明智的,因为宏常常是非常不直观的。然而,正如您已经注意到的,在某些情况下,它们是非常有用的。宏的问题在于它们似乎是格式化的灵丹妙药,直到很久之后,您才意识到存在着严重的根本问题。

话虽如此,他们是用语言表达的。他们有用处。所以,如果你花时间去理解它们,那么你就可以开始明智地使用它们了。宏的两个主要问题是:

  1. 它们是全球的象征,在最不可能的地方造成了令人惊讶的替代。
  2. 当宏实际编译成其他东西时,编写一个看起来像你想要的宏是很容易的。

全局符号

没有什么比让别人的符号和你的代码搞砸更糟糕的了。如果有人定义了一个宏,并且它与您的代码相冲突,那么这个错误几乎总是不可读的。考虑最邪恶的一个,#define max (a, b) ((a)>(b) ? (a) : (b)),它被定义为windows.h的一部分。如果您一直在编写Linux应用程序,那么当您有一个名为“max”的本地应用程序时,您会发现这种情况突然发生了。

通过你选择的#undef符号在结尾,这是很明显的你意识到这一点。你在确保你的定义不会伤害别人方面做得很好。然而,你仍然随心所欲地使用与你相同的名字。小心点干得好,但你还没脱离险境。

**意外汇编**

人们不喜欢宏的原因是他们经常做一些你没有预料到的事情。例如,在最大的例子中,a可以得到两次评估。此外,宏和逗号也不能很好地发挥。宏通常不知道括号,所以像max(getValue(1, 2), getValue(3, 4))这样的东西会给您带来错误的惊喜。您的代码没有任何这些,但是当您试图修改代码样式规则时,始终要注意成本。

然而,你确实有一些有趣的小道消息。例如,您知道不应该在宏中说std::enable_if吗?这将对名为“std”的命名空间进行特定于上下文的搜索。在错误的情况下,这可能会引起问题。正确的措辞是::std::enable_if,它强制它在全局范围内查找正确的std。

想别人

所以,在你的例子中,宏可以工作。你一般都会想清楚。现在,让我们将这些代码带到业务场景中。开发人员的宏实践要比您在这段代码中要少得多。他们将发展风格习惯,采取部分你的写作方式。你真的有足够的可读性来保证潜在的代码混乱吗?

Options

那你还能做什么?由于宏定义通常被视为“很难理解”,因此我觉得编写几个结构来帮助解决这个问题是没有问题的。通过使用structs,我可以避免宏的全局问题,它们总是按照您期望的方式编译。

考虑使用模板参数,如下所示:

代码语言:javascript
复制
template <template <typename> Pred, T>
struct subfail_unless
: std::enable_if<Pred<typename std::decay<T>::type> >
{ };

template<typename T,
         typename subfail_unless<std::is_integral, T>::value >
explicit Object( T&& t      ) { init(long  (t)); }

template<typename T,
         typename subfail_unless<std::is_floating_point, T>::value >
explicit Object( T&& t, ... ) { init(double(t)); }

我为什么喜欢这个:

  • 如果您正在编写这样的代码,您必须了解SFINAE,所以您不会受到模板的威胁。
  • 你只是在读这篇文章,你不需要知道它为什么会起作用,你只需要认识到那些重要的单词。

考虑一下这有多少额外的字符。

代码语言:javascript
复制
macros:              SUBFAIL_UNLESS(     IS_FLOATING       (T))
templates:  typename subfail_unless<std::is_floating_point, T>::value
---------------------------------------------------------------------
difference: typename                std::                     ::value
  • 这段代码现在可以在头文件中重用,而不需要在任何您想使用它的地方使用#define。它现在只使用名称空间的结构,所以它和任何其他代码一样安全。
票数 6
EN

Code Review用户

发布于 2014-12-07 14:59:48

使用C++ 11,您可以用“使用别名”替换这些宏。在C++ 11中,我们可以很容易地执行模板混叠。见:输入别名/别名模板

票数 4
EN

Code Review用户

发布于 2014-12-14 10:50:40

注意科特·阿蒙的批评,我已经取代了我所有的宏。

我同意将名称空间与#define混为一谈是很丑陋的,这可能会覆盖其他名称空间,特别是在一个可以在所有项目中都包含-d的头文件中。

我对由此产生的语法感到满意,它似乎在清晰性/清晰性方面没有明显的损失,加上XCode中的语法高亮功能(它将宏设置为红色,非常难看)!

代码语言:javascript
复制
    // old
    #define DECAY(T)        typename std::decay<T>::type

    #define IS_INTEGRAL(T)  std::is_integral< DECAY(T) >::value

    #define IS_FLOATING(T)  std::is_floating_point< DECAY(T) >::value

    #define SUBFAIL_UNLESS(PRED) \
                            typename std::enable_if<PRED, int>::type = 0

    // new
    template< typename T> 
    using decay_t = typename std::decay<T>::type;

    template< bool pred>  
    using subfail_unless_t = typename std::enable_if< pred, int >::type;

    template< typename T>  
    static bool is_integral() { return std::is_integral      < decay_t<T> >::value; }
    template< typename T>  
    static bool is_floating() { return std::is_floating_point< decay_t<T> >::value; }

    template< typename T>  
    using subfail_unless_integral_t = subfail_unless_t< is_integral<T>() >;
    template< typename T>  
    using subfail_unless_floating_t = subfail_unless_t< is_floating<T>() >;

    // - - - 

    // old
    #define IS_OBJECT(T)  \
                        std::is_base_of< Object, T >::value

    #define TEMPLATE_TU \
        template <  typename T,  typename U,  SUBFAIL_UNLESS( IS_OBJECT(T) || IS_OBJECT(U) )  >

    template<typename T, SUBFAIL_UNLESS(IS_INTEGRAL(T)) > 
    explicit Object( T&& t ) : Object{ pyob_from_integral(t) }  { }
    template<typename T, SUBFAIL_UNLESS(IS_FLOATING(T)) > 
    explicit Object( T&& t ) : Object{ pyob_from_floating(t) }  { }


    // new
    template< typename T>  
    static bool is_object() { return std::is_base_of< Object, T >::value; }

    template< typename T, typename U>  
    using subfail_if_neither_is_object_t = 
                        subfail_unless_t< is_object<T>() || is_object<U>() >;

    template<typename T, subfail_unless_integral_t<T> = 0> 
    explicit Object( T&& t ) : Object{ pyob_from_integral(t) }  { }
    template<typename T, subfail_unless_floating_t<T> = 0> 
    explicit Object( T&& t ) : Object{ pyob_from_floating(t) }  { }
票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/71946

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档