0

While attempting to solve this (un)problem, I noticed a very strange behaviour which in short made possible that a bool is false and its ! (not value) is also false. I would like to know how this is possible. The code that causes this follows:

template<typename T, typename TID = unsigned int>
struct AId {
  typedef AId<T, TID> type;
  typedef T handled_type;
  typedef TID value_type;

private:
  value_type id;

  template<typename _T> struct IsIncrementable
  {
    template<typename _U> using rm_ref = typename std::remove_reference<_U>::type;
    typedef char (&yes)[1];
    typedef char (&no)[2];
    template<class _U>
    static yes test(_U *data, typename std::enable_if<
                      std::is_same<_U, rm_ref<decltype(++(*data))>>::value
                    >::type * = 0);
    static no test(...);
    static const bool value = sizeof(yes) == sizeof(test((rm_ref<_T> *)0));
  };

public:
  explicit AId(const value_type &id) : id(id) {}

...

  //IsIncrementable<value_type>::value is false:
  //error: no type named 'type' in 'struct std::enable_if<false, int>'
  template<typename std::enable_if<IsIncrementable<value_type>::value, int>::type = 0>
  type operator++(int /*postfix*/) { type old(id); ++id; return old; }

  //!IsIncrementable<value_type>::value is also false:
  //error: no type named 'type' in 'struct std::enable_if<false, int>'
  template<typename std::enable_if<!IsIncrementable<value_type>::value, int>::type = 0>
  type operator++(int /*postfix*/) { type old(id); ++id; return old; }
};

How is it possible that IsIncrementable<value_type>::value is false and !IsIncrementable<value_type>::value is also false?

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
Olivetree
  • 424
  • 3
  • 11
  • See the comment in the code, the error I get is enable_if in both, so arguments to both enable_if are false. – Olivetree Oct 12 '16 at 15:03
  • Looking at this, Barry's answer even solves the problem you referenced [here](https://stackoverflow.com/questions/39998793/sfinae-to-have-a-class-member-only-if-possible), with a simpler answer than mine. – Guillaume Racicot Oct 12 '16 at 16:02
  • @Guillaume Racicot so it also doesn't give false positives for other SFINAE uses? (not so fun fact: I have already used similar code before, but dropped it in favor of a different approach and no longer have it) – Olivetree Oct 12 '16 at 16:41
  • Exactly. No false positive. – Guillaume Racicot Oct 12 '16 at 16:59

2 Answers2

4

SFINAE only applies in the immediate context of template instantiation. Here's a shorter example:

template <class T>
struct X {
    template <std::enable_if_t<std::is_pointer<T>::value, int> = 0>
    void foo() {
    }
};

T is already known by the time foo is instantiated, so it isn't a failure that happens during substitution for that function template. It's a hard error. You can't even instantiate X<int> because enable_if_t<false, int> is already ill-formed, regardless of whether or not you call foo.

You'll have to introduce a defaulted type parameter that would actually fall into the immediate context:

template <class T>
struct X {
    template <class U=T, std::enable_if_t<std::is_pointer<U>::value, int> = 0>
    void foo() {
    }
};

Now, SFINAE-ing on U is fine - U is a template parameter local to this function so that instantiation will be delayed until this function is used. So X<int>{} is fine and X<int>{}.foo() will fail because overload resolution fails to find a viable overload - this foo() was just removed.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • While this is a nice explanation on why SFINAE wouldn't work in my code, it does not answear the question of how can a bool value be false and its `!` also be false. If I change the code the functions to – Olivetree Oct 12 '16 at 16:22
  • (forgot shift before enter, can't edit comment) `static_assert(IsIncrementable::value, "It cannot be incrementable"); `static_assert(!IsIncrementable::value, "It has to be incrementable");` it still doesn't compile. I now realize I should've chosen this exposition instead of the functions. – Olivetree Oct 12 '16 at 16:25
  • @Olivetree The answer to the question is that SFINAE doesn't work in your code, so whichever one is `false` is hard error. It's not that the bool is a both true and false simultaneously. – Barry Oct 12 '16 at 16:25
  • But that's exactly my problem: they both show up with the same error: std::enable_if has no type. Meanwhile I have figured my exact problem and will post an answear. – Olivetree Oct 12 '16 at 16:27
  • @Barry: and s/type/time/ in that sentence? Or am I just failing to parse? – Toby Speight Oct 12 '16 at 16:51
0

This is probably unfair to other members, as the problem doesn't lie within the code of the template, but rather with the instantiations.

@Barry's answear explains why SFINAE wouldn't work here, giving hard errors instead. But if I replace the functions with:

  static_assert(IsIncrementable<value_type>::value, "It cannot be incrementable");
  static_assert(!IsIncrementable<value_type>::value, "It has to be incrementable");

I still get errors in both assertions. The problem is that I was instantiating the template with two different classes. For one case IsIncrementable<value_type>::value was true and for the other IsIncrementable<value_type>::value was false. Since the error would show up on false, this gave me the impression that it was always false, even its ! value.

Olivetree
  • 424
  • 3
  • 11