Перегрузка функции с помощью std :: enable_if, чтобы избежать ошибки подстановки шаблона - PullRequest
0 голосов
/ 17 апреля 2019

Я хочу написать две функции шаблона, чтобы одна перехватывала конкретный случай, а другая - все другие случаи, которые не соответствуют первому случаю. Я пытаюсь использовать std :: enable_if, чтобы поймать конкретный случай, но компилятор все еще терпит неудачу с неоднозначным соответствием. Как я могу написать эти перегруженные функции, чтобы неоднозначность разрешалась компилятором? (Я использую g ++)

Я попытался написать следующий код (это упрощенный пример, который воспроизводит проблему):

struct resource1_t{
};

struct resource2_t{
};

template <typename R, typename V>
struct unit_t{
  typedef R resource_t;
  typedef V value_t;
  unit_t(value_t const& value):v(){}
  value_t v;
  value_t calcValue(resource_t const& r)const{return v;}
};

// Specific case (U::resource_t == R)
template <typename U, typename R, typename=std::enable_if_t<std::is_same_v<typename U::resource_t,R>>>
      typename U::value_t callCalcValue(U const& u, R const& r){
        return u.calcValue(r);
      }

 // General case (U::resource_t != R)
 template <typename U, typename R>
      typename U::value_t callCalcValue(U const& u, R const& r){
        // Fail immediately!
        assert(!"Unit resource does not match");
        return U::value_t();
      }

int main()
{
    // Create an array of unit variants
   typedef unit_t<resource1_t,int> U1;
   typedef unit_t<resource2_t,float> U2;
   std::vector<std::variant<U1,U2>> units;
   units.emplace_back(U1(1));
   units.emplace_back(U2(1.0f));

   // Create a parallel array of resources
   std::vector<std::variant<resource1_t,resource2_t>> resources;
   resources.emplace_back(resource1_t());
   resources.emplace_back(resource2_t());

   // Call calcValue for each unit on the parallel resource
   for(int i(0); i<units.size(); ++i){
       std::visit([&](auto&& unit){
           std::visit([&](auto&& resource){
             // Fails to compile with substitution failure...
             //std::cout << unit.calcValue(resource) << "\n";

             // Results in ambiguous call compile error...
             std::cout << callCalcValue(unit,resource) << "\n";
           },resources[i]);
       },units[i]);
   }
}

Я ожидал, что компилятор сопоставит все случаи, когда std::is_same_v<U::resource_t,R>, с конкретным случаем и все другие комбинации с общим случаем, вместо этого компилятор не сможет сказать, что функция неоднозначна. Я также попробовал ! std::is_same для второго определения, и компилятор завершился ошибкой с error: redefinition of ... callCalcValue()...

Ответы [ 2 ]

2 голосов
/ 17 апреля 2019

Вот пример сокращенного примера:

template <typename T, typename R, typename=std::enable_if_t<std::is_same_v<R, int>>
void foo(T, R); // #1

template <typename T, typename R>
void foo(T, R); // #2

foo(some_t{}, some_r{});

На самом деле не имеет значения, какое конкретно ограничение sfinae, поэтому я выбрал простой.Теперь, если ограничение (в данном случае is_same_v<R, int>) равно , а не , замена на #1 не выполняется, и у нас остается один единственный кандидат: #2.

Ноесли замена удалась, у нас есть два кандидата.Они в равной степени жизнеспособны, их нечего отличить.Следовательно, вызов неоднозначен!

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

template <typename T, typename R, std::enable_if_t<std::is_same_v<R, int>, int> = 0>
void foo(T, R); // #1

template <typename T, typename R, std::enable_if_t<!std::is_same_v<R, int>, int> = 0>
void foo(T, R); // #2

Это обеспечивает жизнеспособность ровно одного из двух.

Другим способом было бы переадресация черты в отдельный набор перегрузки:

template <typename T, typename R>
void foo_impl(T, R, std::true_type); // #1

template <typename T, typename R>
void foo_impl(T, R, std::false_type); // #2

template <typename T, typename R>
void foo(T t, R r) {
    return foo_impl(t, r, std::is_same<R, int>{}); // NB: not _v
}

Другим способом было бы просто написать одну перегрузку и использовать if constexpr внутри:

template <typename T, typename R>
void foo(T, R) {
    if constexpr (std::is_same_v<R, int>) {
        // #1
    } else {
        // #2
    }
}

Будущий способ (в C ++ 20) будет использовать понятия:

template <typename T, typename R>
    requires std::is_same_v<R, int>
void foo(T, R); // #1

template <typename T, typename R>
void foo(T, R); // #2

Это сделает #1 "более ограниченным, чем" #2, если оно жизнеспособно, поэтому было бы предпочтительным.

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

Интересно, сработает ли простая перегрузка:

// General case (U::resource_t != R)
template <typename U, typename R>
     typename U::value_t callCalcValue(U const& u, R const& r){
       // Fail immediately!
       assert(!"Unit resource does not match");
       return U::value_t();
     }

//Specific case (U::resource_t == R) overloads the general case
template <typename U>
      typename U::value_t callCalcValue(U const& u,  typename U::resource_t const& r){
        return u.calcValue(r);
      }
...