变长模板特化、std::enable_if、SFINAE

3
一个单例类向客户端公开此方法以获取该实例:
template< typename ... Args >
static T & GetInstance( Args && ... args )
{
    if ( ! m_instance )
    {
        m_instance = new T( std::forward< Args >( args ) ... );
    }

    return * m_instance;
}

但是对于没有默认构造函数的类,总是需要传递参数很麻烦。如果实例已经被创建,允许用户只调用以下内容会更好:

auto & instance = GetInstance( );

起初,我认为要使这个工作正常运行,只需要专门化模板方法,例如:

// Wrong Version
template< >
static T & GetInstance< >( )
{
    if ( ! instance )
    {
        throw std::runtime_error(
            "Tried to get instance of non initialized singleton with no"
            " default constructor." );
    }

    return * instance;
}

对于有默认构造函数的类而言,将使用这种特化版本 代替 更普遍的版本。我希望只有在 T 没有默认构造函数时才使用这种特化。

因此,我尝试进行了一些修改:

// Right Version
template< >
static auto GetInstance< >( ) ->
    typename std::enable_if<
        ! std::is_default_constructible< T >::value , T & >::type 
{
    if ( ! instance )
    {
        throw std::runtime_error(
            "Tried to get instance of non initialized singleton with no"
            " default constructor." );
    }

    return * instance;
}

所以这个方法起作用了,但是我对整个过程感到困惑。首先,我使用的方式是否是处理它的正确方式?难道我不应该将enable_if<>用作参数或模板参数,而不是返回类型吗?

编译器在这里是如何工作的?当它只是简单的模板特化(在错误版本中)时,我想编译器意识到更专业的版本对于调用没有参数的GetInstance()的代码(T是具有默认构造函数的类)更好。

对于带有enable_if<>的版本,编译器开始思考也使用更专业的版本会更好,但是代码却不符合规范。所以它回退到通用版本?这也被称为SFINAE吗?

1个回答

4
方便的经验法则:不要专门化,而是重载。
template <class... Args>
static T& GetInstance(Args&&... );

template <class U=T, std::enable_if_t<!std::is_default_constructible<U>::value, int> = 0>
static T& GetInstance();

如果T是默认可构造的,你只有一个可行的重载。如果不是,则当Args为空时第二个更加专业化,并且更受欢迎。
注意:这种设计似乎有问题。

谢谢您的快速回答!重载后似乎更清晰了。但是为什么要使用 class U=T,然后再使用 U?这与重载解决有关吗? - Tarc
@Tarc SFINAE仅适用于直接上下文。如果您执行is_default_constructible<T>::value,其中T不是模板参数,则会立即评估并出现硬错误。 - Barry

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