Как отключить множество перегрузок методов с помощью SFINAE? - PullRequest
1 голос
/ 06 апреля 2019

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

// Type1 and Type2 are class template arguments
Type2 overloadedMethod(Type1);
Type1 overloadedMethod(Type2);

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

До сих пор я пытался решить эту проблему с использованием SFINAE и std::enable_if, но без особых усилий.успех.Моя текущая реализация выглядит следующим образом:

template <typename Type1, typename Type2>
class OverloadDisablingClass
{
  public:
    template <typename MethodType1 = Type1, typename MethodType2 = Type2,
              std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    Type2 overloadedMethod(Type1);
    template <typename MethodType1 = Type1, typename MethodType2 = Type2,
              std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    Type1 overloadedMethod(Type2);
};

, и я пытаюсь использовать ее следующим образом:

// attempt to disable set of overloaded methods:
OverloadDisablingClass<int, std::string> overloadClassWithFullApi;
overloadClassWithFullApi.overloadedMethod(1);
overloadClassWithFullApi.overloadedMethod("one");
OverloadDisablingClass<std::string, std::string> overloadClassWithPartialApi;
// above line doesn't compile, overloads with same signature

К сожалению, когда я пытаюсь скомпилировать программу, gcc завершается ошибкой со следующей ошибкойв строке, где я создаю экземпляр моего класса с теми же аргументами шаблона:

error: 'template<class MethodType1, class MethodType2, typename std::enable_if<(! std::is_same<MethodType1, MethodType2>::value), bool>::type <anonymous> > Type1 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type2) [with MethodType1 = MethodType1; MethodType2 = MethodType2; typename std::enable_if<(! std::is_same<MethodType1, MethodType2>::value), bool>::type <anonymous> = <enumerator>; Type1 = std::__cxx11::basic_string<char>; Type2 = std::__cxx11::basic_string<char>]' cannot be overloaded
 Type1 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type2)
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: with 'template<class MethodType1, class MethodType2, typename std::enable_if<(! std::is_same<MethodType1, MethodType2>::value), bool>::type <anonymous> > Type2 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type1) [with MethodType1 = MethodType1; MethodType2 = MethodType2; typename std::enable_if<(! std::is_same<MethodType1, MethodType2>::value), bool>::type <anonymous> = <enumerator>; Type1 = std::__cxx11::basic_string<char>; Type2 = std::__cxx11::basic_string<char>]'
 Type2 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type1)
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

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

Полный исходный код моего примера, используемый для проверки поведения:

// main.cpp

#include "methoddisablingclass.hpp"
#include "overloaddisablingclass.hpp"

#include <string>

int main()
{
    // attempt to disable set of overloaded methods:
    OverloadDisablingClass<int, std::string> overloadClassWithFullApi;
    overloadClassWithFullApi.overloadedMethod(1);
    overloadClassWithFullApi.overloadedMethod("one");
    OverloadDisablingClass<std::string, std::string> overloadClassWithPartialApi;
    // above line doesn't compile, overloads with same signature

    // simpler attempt to test the SFINAE, disables a single method, works as expected
    MethodDisablingClass<std::string> simpleClassWithFullApi;
    simpleClassWithFullApi.alwaysSuitableMethod();
    simpleClassWithFullApi.sometimesUnsuitableMethod();
    MethodDisablingClass<int> simpleClassWithPartialApi;
    simpleClassWithPartialApi.alwaysSuitableMethod();
    // simpleClassWithPartialApi.sometimesUnsuitableMethod(); // <- fails to compile, as is intended

    return 0;
}
// methoddisablingclass.hpp

#ifndef METHODDISABLINGCLASS_HPP
#define METHODDISABLINGCLASS_HPP

#include <iostream>
#include <type_traits>

template <typename Type>
class MethodDisablingClass
{
  public:
    template <typename MethodType = Type, std::enable_if_t<!std::is_integral<MethodType>::value, bool> = true>
    void sometimesUnsuitableMethod();
    void alwaysSuitableMethod();
};

template <typename Type>
template <typename MethodType, std::enable_if_t<!std::is_integral<MethodType>::value, bool>>
void MethodDisablingClass<Type>::sometimesUnsuitableMethod()
{
    std::cout << __func__ << "\n";
}

template <typename Type>
void MethodDisablingClass<Type>::alwaysSuitableMethod()
{
    std::cout << __func__ << "\n";
}

#endif  // METHODDISABLINGCLASS_HPP
// overloaddisablingclass.hpp

#ifndef OVERLOADDISABLINGCLASS_HPP
#define OVERLOADDISABLINGCLASS_HPP

#include <iostream>
#include <type_traits>

template <typename Type1, typename Type2>
class OverloadDisablingClass
{
  public:
    template <typename MethodType1 = Type1, typename MethodType2 = Type2,
              std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    Type2 overloadedMethod(Type1);
    template <typename MethodType1 = Type1, typename MethodType2 = Type2,
              std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    Type1 overloadedMethod(Type2);
};

template <typename Type1, typename Type2>
template <typename MethodType1, typename MethodType2, std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool>>
Type2 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type1)
{
    std::cout << __func__ << " for Type1\n";
    return Type2();
}

template <typename Type1, typename Type2>
template <typename MethodType1, typename MethodType2, std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool>>
Type1 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type2)
{
    std::cout << __func__ << " for Type2\n";
    return Type1();
}

#endif  // OVERLOADDISABLINGCLASS_HPP

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

Iнужно решение, совместимое с C ++ 14, но если есть возможные улучшения в решении, которые зависят от C ++ 17 (или C ++ 20? :-)), мне, безусловно, это интересно.

Обратите внимание, что после того, как я не смог сделать это с помощью SFINAE, я начал думать об использовании простой старой специализации шаблона и использовать наследование, чтобы избежать дублирования кода (Фактический класс, для которого я хотел бы использовать это, несколько сложнее.чем тот, что в примере).Но эта проблема заинтересовала меня, поэтому я хотел бы узнать решение независимо от того, какой подход я в конечном итоге использую.

Ответы [ 2 ]

1 голос
/ 06 апреля 2019

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

(обратите внимание, я имею в виду параметры шаблона методов (MethodType1 и MethodType2), а не захваченные типы шаблонов Type1 и Type2)

Мне кажется, что быстрое исправление зависимости подписи от параметров шаблона метода работает:

#include <type_traits>
#include <string>

template <typename Type1, typename Type2>
class OverloadDisablingClass
{
  public:
    template <typename MethodType1 = Type1, typename MethodType2 = Type2, std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    MethodType2 overloadedMethod(Type1) {}
    template <typename MethodType1 = Type1, typename MethodType2 = Type2, std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    MethodType1 overloadedMethod(Type2) {}
};

int main() {
    OverloadDisablingClass<int, std::string> overloadClassWithFullApi;
    overloadClassWithFullApi.overloadedMethod(1);
    overloadClassWithFullApi.overloadedMethod("one");
    OverloadDisablingClass<std::string, std::string> overloadClassWithPartialApi;
    return 0;
}
0 голосов
/ 06 апреля 2019

Один из способов подойти к проблеме под другим углом - это специализировать OverloadDisablingClass для случая, когда он получает два одинаковых параметра.

template <typename Type1, typename Type2>
class OverloadDisablingClass
{
  public:
    Type2 overloadedMethod(Type1);
    Type1 overloadedMethod(Type2);
};

// Implement methods here

template <typename Type1>
class OverloadDisablingClass<Type1, Type1>
{
  public:
    Type1 overloadedMethod(Type1);
};

// Implement specialization here
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...