Почему 'enable_if' нельзя использовать для отключения этого объявления здесь - PullRequest
0 голосов
/ 06 июня 2018
#include<string>
#include<type_traits>

template<typename... Args>
class C {
public:
    void foo(Args&&... args) {      

    }

    template<typename = std::enable_if_t<(0 < sizeof...(Args))>>
    void foo(const Args&... args) {     

    }
};

int main() {
    C<> c;
    c.foo();
    return 0;
}

Вышеупомянутый код работает как указано (мной :)) и вызывает void foo(Args&&... args) во время выполнения в msvc 2015 , но один и тот же код не может даже скомпилироваться в обоих gcc 7.3 и clang 6.0.0 с ошибкой:

ошибка: нет типа с именем 'type' в 'std :: enable_if';«enable_if» нельзя использовать для отключения этой декларации

Я хочу понять, что не так с приведенным выше кодом и как его можно исправить?

Ответы [ 2 ]

0 голосов
/ 06 июня 2018

Как лучше объяснил Клаус, ваш оригинальный код не работает, потому что std::enable_if_t необходимо проверить шаблон самого метода и недостаточно списка шаблонов класса.

Я предлагаюупрощенная альтернатива решения Клауса.

Прежде всего, вам нужен параметр шаблона для проверки;Вы можете использовать один со значением по умолчанию, выведенным из параметра шаблона класса (Args...).

Конечно, вы можете использовать тип, принимающий std::tuple из Args..., но с учетом того, чтоВас интересует только число Args... параметров, я считаю, что проще использовать шаблонный параметр std::size_t, инициализированный числом Args...

template <std::size_t N = sizeof...(Args)>
std::enable_if_t<N> foo (Args const & ... args)
 { std::cout << "N args" << std::endl; }  

Что касается версии с нулевым аргументом, то здесьнет необходимости делать его шаблонной версией;Вы можете просто написать его с нулевыми параметрами

void foo ()
 { std::cout << "zero args" << std::endl; }

В случае нуля Args... версия без шаблона имеет приоритет над шаблонной.

Ниже приводится полная компиляцияпример

#include <iostream>
#include <type_traits>

template <typename... Args>
struct C
 {
   void foo ()
    { std::cout << "zero args" << std::endl; }

   template <std::size_t N = sizeof...(Args)>
   std::enable_if_t<N> foo(const Args&... args)
    { std::cout << "N args" << std::endl; }
};

int main ()
 {
   C<>    c0;
   C<int> c1;
   c0.foo();
   c1.foo(42);
 }
0 голосов
/ 06 июня 2018

SFINAE работает только для выводимых аргументов шаблона.В вашем случае ваш метод не зависит от какого-либо параметра из вызова метода, поэтому он не находится в выведенном контексте.Все уже известно, пока создаем экземпляр самого класса.

MSVC просто неверен в этом случае.

Обходной путь:

template<typename... Args>
class C
{
    public:
        template< typename U = std::tuple<Args...>>
            std::enable_if_t< (std::tuple_size<U>::value > 0 ) > foo(const Args&...)
            {
                std::cout << "Args  > 0 type " << std::endl;
            }

        template< typename U = std::tuple<Args...>>
            std::enable_if_t< (std::tuple_size<U>::value == 0)> foo(const Args&...)
            {
                std::cout << "Args 0 type " << std::endl;
            }
};

int main()
{
    C<>{}.foo();
    C<int>{}.foo(1);
}

Я не знаю, зачем вам такиеперегрузка, потому что, если список параметров пуст, вы просто должны написать перегрузку для такого без каких-либо вещей SFINAE.

, если ваш компилятор не устарел (только c ++ 14), это намного проще сиспользуя constexpr if:

template <typename... Args>
struct C
{
    void foo (const Args&... args)
    {
        if constexpr ( sizeof...(args) == 0)
        {
            std::cout << "0" << std::endl;
        }
        else
        {
            std::cout << ">0" << std::endl;
        }
    }
};

int main ()
{
    C<>    c0;
    C<int> c1;
    c0.foo();
    c1.foo(42);
}

РЕДАКТИРОВАТЬ после комментария:

Чтобы избежать SFINAE, вы также можете использовать специализированные классы шаблонов, такие как:

// provide common stuff here
template <typename ... ARGS>
class CAll { protected: void DoSomeThing(){ std::cout << "Do some thing" << std::endl; } };

template<typename ... ARGS>
class C;

// special for no args
template<>
class C<>: public CAll<>
{   
    public:
        void foo() 
        {
            std::cout << "none" << std::endl; 
            this->DoSomeThing();
        }   
};  

//special for at minimum one arg
template<typename FIRST, typename ... REST>
class C<FIRST, REST...>: public CAll<FIRST, REST...>
{   
    public:
        void foo( FIRST&, REST&... )
        {   
            std::cout << "lvalue" << std::endl;
            this->DoSomeThing();
        }

        void foo( FIRST&&, REST&&... )
        {   
            std::cout << "rvalue" << std::endl;
            this->DoSomeThing();
        }   
};  

int main()
{   
    int a;
    C<>{}.foo();
    C<int>{}.foo(1);
    C<int>{}.foo(a);
}
...