0

The following code comes from cxx-prettyprint , which implements detecting whether type T has a corresponding member

#include<iostream>
#include<vector>
#include<type_traits> 
using namespace std;
struct sfinae_base
{
  using yes = char;
  using no = yes[2];
};

template <typename T>
struct has_const_iterator : private sfinae_base
{
private:
  template <typename C> static yes& test(typename C::const_iterator*);
  template <typename C> static no& test(...);
public:
  static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
  using type = T;
};

template <typename T>
struct has_begin_end : private sfinae_base
{
private:
  template <typename C>
  static yes& f(typename std::enable_if<
    std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::begin)),
    typename C::const_iterator(C::*)() const>::value>::type*);

  template <typename C> static no& f(...);

  template <typename C>
  static yes& g(typename std::enable_if<
    std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::end)),
    typename C::const_iterator(C::*)() const>::value, void>::type*);

  template <typename C> static no& g(...);

public:
  static bool const beg_value = sizeof(f<T>(nullptr)) == sizeof(yes);
  static bool const end_value = sizeof(g<T>(nullptr)) == sizeof(yes);
};

int main()
{
  vector<int> sa{ 1,2,3,4,5 };
  cout << has_const_iterator<vector<int>>::value;
  cout<<has_begin_end<vector<int>>::beg_value;
  cout << has_begin_end<vector<int>>::end_value;
  return 0;
}

run it online

Some time later I read someone else's blog and changed it to this

#include<utility>
#include<iostream>
#include<vector>
using namespace std;
template <typename T>
struct has_const_iterator
{
private:
  template <typename U>
  static constexpr decltype(std::declval<U::const_iterator>(), bool()) test(int) { return true; }
  template <typename U>
  static constexpr bool test(...) { return false; }
public:
  static const bool value = test<T>(1); //为什么这个不对?
  using type = T;
};
template <typename T>
struct has_begin_end
{
private:
  template <typename U>
  static constexpr decltype(std::declval<U>().begin(), bool()) f(int) { return true; }
  template <typename U>
  static constexpr bool f(...) { return false; }
  template <typename U>
  static constexpr decltype(std::declval<U>().end(), bool()) g(int) { return true; }
  template <typename U>
  static constexpr bool g(...) { return false; }

public:
  static bool const beg_value = f<T>(2);
  static bool const end_value = g<T>(2);
};
int main()
{
  vector<int> sa{ 1,2,3,4,5 };
  cout << has_const_iterator<vector<int>>::value;
  cout<<has_begin_end<vector<int>>::beg_value;
  cout << has_begin_end<vector<int>>::end_value;
  return 0;
}


run it online

For the first piece of code it shows 111
For the second piece of code it shows 011
Snippet 2 was working fine a few months ago, but not now.
My question is, what's wrong with the second one,and why it was good before and now goes wrong?

Added: I found this, what does he mean by introduce ODR violations?

Kargath
  • 450
  • 5
  • 15
  • Why do you think the "Added" is related? That answer talks about the issue when you have two different definitions of an operator<< for eg `std::vector` in your project, eg because some libxy provides one and some other libabc provides one, and both placed them in global namespace. – 463035818_is_not_an_ai Oct 31 '22 at 12:48
  • Why do you include the `has_begin_end` part if it doesn't change? – Nelfeal Oct 31 '22 at 12:52
  • please add the missing includes so that others can compile the code – 463035818_is_not_an_ai Oct 31 '22 at 12:54
  • @Nelfeal You're right, I can remove it from the minimal reproducible code. But I thought maybe someone would give a c++20 based implementation, so I put it there so others would rewrite it too by the way, haha. – Kargath Oct 31 '22 at 12:57
  • @Kargath If you want a C++20 (I assume concept-based?) implementation, then ask another question. `has_begin_end` does not belong in this one. – Nelfeal Oct 31 '22 at 13:05
  • By the way, I know why it it works before and didn't work now , because msvc used to allow not to write typename, it's more strict now – Kargath Oct 31 '22 at 13:30

2 Answers2

2

If you force a call the overload of has_const_iterator::test that returns true (by removing the other):

#include <utility>
#include <vector>
#include <iostream>

template <typename T>
struct has_const_iterator
{
private:
  template <typename U>
  static constexpr decltype(std::declval<U::const_iterator>(), bool()) test(int) { return true; }
  //template <typename U>
  //static constexpr bool test(...) { return false; }
public:
  static const bool value = test<T>(1);
};

int main()
{
  std::cout << has_const_iterator<std::vector<int>>::value;
}

then you get this error message (more or less):

error: dependent-name 'U::const_iterator' is parsed as a non-type, but instantiation yields a type
note: say 'typename U::const_iterator' if a type is meant

It tells you exactly what's wrong: typename is missing before U::const_iterator. The reason is that U::const_iterator is a dependent name (it could be a type or a variable, for example, depending on what U is exactly), so you need to specify that it refers to a type.

Note that the previous version does use typename:

template <typename C> static yes& test(typename C::const_iterator*);
//-------------------------------------^^^^^^^^

This version works:

#include <utility>
#include <vector>
#include <iostream>

template <typename T>
struct has_const_iterator
{
private:
  template <typename U>
  static constexpr decltype(std::declval<typename U::const_iterator>(), bool()) test(int) { return true; }
  template <typename U>
  static constexpr bool test(...) { return false; }
public:
  static const bool value = test<T>(1);
};

int main()
{
  std::cout << has_const_iterator<std::vector<int>>::value;
}

Demo

Nelfeal
  • 12,593
  • 1
  • 20
  • 39
  • its not really "unecessary code" but its required to get `false` when SFINAE kicks in. Anyhow, nice way to see the actual error, by turning SFINAE into a hard error that gets reported – 463035818_is_not_an_ai Oct 31 '22 at 13:01
  • 1
    @463035818_is_not_a_number I haven't removed the `false` overload, I commented it out. The unnecessary code is everything else from the question, including the whole `has_begin_end` part and the `using type = T;` line. – Nelfeal Oct 31 '22 at 13:03
  • ah ok, now I understand. I tend to ignore comments, hence I read "removed" as also refering to the `// ....` – 463035818_is_not_an_ai Oct 31 '22 at 13:14
  • @463035818_is_not_a_number I cleaned it up since it could cause confusion. – Nelfeal Oct 31 '22 at 13:15
  • Thank you for teaching me how to debug such problems – Kargath Oct 31 '22 at 13:31
1

You are missing a typename because U::const_iterator is a dependant name.

template <typename T>
struct has_const_iterator
{
private:
  template <typename U>
  static constexpr decltype(std::declval<typename U::const_iterator>(), bool()) test(int) { return true; }
                                         // ^^ 
  template <typename U>
  static constexpr bool test(...) { return false; }
public:
  static const bool value = test<T>(1); //为什么这个不对?
  using type = T;
};

After fixing that it prints expected results: https://godbolt.org/z/jP9G95Pb1l.

I did not find yet a compiler that produced output 111 without the typename though.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thank you, I understand the meaning of [dependant name](https://en.cppreference.com/w/cpp/language/dependent_name), I have seen it while learning c++, but it appears in specific question, i didn't realize it – Kargath Oct 31 '22 at 13:13