enable_if vs static_assert wielki pojedynek, zobacz kto zwycięży, masakra!

0

No więc tak, w offline'owej ankiecie wyszło mi 2:2, dwie osoby za enable_if, dwie za static_assert, także dlaczego by nie zapytać forum ;)

Problem i uproszczony przykład, mam klasę, która przechowuje wartość generyczną. Dla jednego, jedynego typu chce uniemożliwić zawołanie jednej metody. I mogę to zrobić na przynajmniej dwa sposoby, każda ma swoje wady i zalet.

enable_if

template<typename T>
struct Holder
{
  T getValue() const { return m_value;}

  template<typename U = T, typename = typename std::enable_if<!std::is_same<U, bool>::value>::type>
  const T& getRefValue() const { return m_value; }
  
  T m_value;
};

static_assert

template<typename T>
struct Holder
{
  T getValue() const { return m_value;}

  const T& getRefValue() const
  {
     static_assert(!std::is_same<T, bool>::value, "Calling getRefValue for bool is not allowed"); 
     return m_value;
  }
  
  T m_value;
};

Dzięki enable_if jeśli mamy dobry edytor dostaniemy błąd już na etapie pisania kodu. Ale za to dzięki static_assert będziemy mieli czytelniejszy błąd kompilacji jeśli już ktoś niepoprawnie metody użyje. Pojawił się też argument że static_assert jakiś taki czytelniejszy. A co o tym myśli forum 4p? Ja tam wolę enable_if, vim take rzeczy ogarnia bez problemu.

I jeszcze bonusowe rozwiązanie, zamiast enable_if można by zmajstrować zamiennik z którym już każdy edytor powinien sobie poradzić:

template<typename T, typename TSelf>
struct ValueRefAccess
{
  const T& getRefValue() { return static_cast<const TSelf*>(this)->m_value; }
};

template<typename T, typename TSelf>
struct ValueRefAccess <bool, TSelf>
{
};

template<typename T>
struct Holder : ValueRefAccess<T, Holder<T>>
{
  T getValue() const { return m_value;}

  T m_value;
};

2

A co z tag dispatchingiem (najczystszym rozwiązaniem do C++14) i constexpr if?

Jak dla mnie każde z rozwiązań należy rozpatrywać przez pryzmat jego charakterystyk, zamiast stwierdzać, że jedno jest lepsze od drugiego (choć tag dispatch > enable_if).

enable if/tag dispatch pozwala na sfinae
static_assert daje czytelne komunikaty błędów

3

Kilka uwag ode mnie:

  1. enable_if i static_assert nie działają tak samo, choć czasem działają podobnie:
    1.1) konstrukcje te nie są sprawdzane w tym samym czasie,
    1.2) użycie enable_if wyrzuca nam funkcje z overload set co umożliwia wykonanie kolejnej we wspomnianym overload set funkcji (konstrukcja znana jako SFINAE). static_assert zwyczajnie zatrzyma kompilacje.
  2. C++17 ma constexpr if, które w wielu przypadkach będzie ładniejsze / czytelniejsze zarówno od static_assert jak i enable_if.
0

Ja wiem Panowie, że enable_if i static_assert to nie to samo, że w innym czasie ewaluowane itd. Porównuję te dwie techniki jako rozwiązanie konkretnego problemu - całkowite wycięcie jednej metody dla jednego typu. Ewentualnie szukam lepszego sposobu ;)

Tag dispatch używałem do wybierania którejś z metod, a consteprx if do zmiany zachowania metody w zależności od typu, chętnie zobaczyłbym przykłady jak koszernie je użyć żeby, powtarzam, wyciąć metode dla jednego typu.

EDIT
Mógłbym w sumie zapytać inaczej, jak wolelibyście zostać poinformowani że nie wolno wam używać metody z jakiejś biblioteki ;)

2

Ale ... po co enable_if czy static_assert? Przecież to sprawę załatwia...

#include <iostream>

template<typename T>
struct Holder
{
  Holder(T a);
  T getValue() const;
  const T& getRefValue() const;
private:
  T m_value;
};

template<typename T>
Holder<T>::Holder(T a): m_value{a}
{
}

template<typename T>
T Holder<T>::getValue() const
{
    return m_value;
};

template<typename T>
const T& Holder<T>::getRefValue() const
{
    return m_value;
}

// Usunięta specjalizacja dla T ==  bool
template<>
const bool& Holder<bool>::getRefValue() const = delete;

int main()
{
    Holder<bool> h(true);
    auto z = h.getValue();
    // To się nie powiedzie bo specjalizacja usunięta... 
    const auto& q = h.getRefValue();
    std::cout << z << ' ' << q << '\n';
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1