Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a constructor so it only accepts input iterators using typeid?

I want to implement a range constructor for a certain object, but I want to restrict it to only accept two input iterators.

I've tried to compile this code with gcc 7.1.0.

File test.cpp

#include <vector>
#include <type_traits>
#include <typeinfo>

template <typename Iterator>
using traits = typename std::iterator_traits<Iterator>::iterator_category;

template <typename T>
class A{
   private:

      std::vector<T> v;

   public:

      template <typename InputIterator,
               typename = std::enable_if_t<
                  typeid(traits<InputIterator>) ==
                  typeid(std::input_iterator_tag)>
               >
      A(InputIterator first, InputIterator last) : v(first, last) {}
};

int main(){
   std::vector<double> v = {1, 2, 3, 4, 5};
   A<double> a(v.begin(), v.end());
}

I get this compilation error with g++ test.cpp -o test:

  test.cpp: In function ‘int main()’:
  test.cpp:27:34: error: no matching function for call to ‘A<double>::A(std::vector<double>::iterator, std::vector<double>::iterator)’
      A<double> a(v.begin(), v.end());
                                    ^
  test.cpp:22:7: note: candidate: template<class InputIterator, class> A<T>::A(InputIterator, InputIterator)
         A(InputIterator first, InputIterator last) : v(first, last) {}
         ^
  test.cpp:22:7: note:   template argument deduction/substitution failed:
  test.cpp: In substitution of ‘template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = ((const std::type_info*)(& _ZTISt26random_access_iterator_tag))->std::type_info::operator==(_ZTISt18input_iterator_tag); _Tp = void]’:
  test.cpp:18:16:   required from here
  test.cpp:19:49: error: call to non-constexpr function ‘bool std::type_info::operator==(const std::type_info&) const’
                     typeid(traits<InputIterator>) ==
  test.cpp:18:16: note: in template argument for type ‘bool’ 
                  typename = std::enable_if_t<
                  ^~~~~~~~
  test.cpp:10:7: note: candidate: A<double>::A(const A<double>&)
   class A{
         ^
  test.cpp:10:7: note:   candidate expects 1 argument, 2 provided
  test.cpp:10:7: note: candidate: A<double>::A(A<double>&&)
  test.cpp:10:7: note:   candidate expects 1 argument, 2 provided

I decided to use default template parameter because is more suitable for constructors. The use of the operator typeid() is because I find it really easy to read when mantaining the code, but I can't get it to work in any way.

Other solutions look very weird and are really obscure (like forcing the InputIterator parameter to have certain methods such as *it or ++it). If there's no way I can do this, I would appreciate a, more or less, easy-to-read solution.

like image 759
Francisco Gallego Salido Avatar asked Aug 08 '17 09:08

Francisco Gallego Salido


2 Answers

In order to do SFINAE you need to assure that evaluation of involving expressions happen at compile time. For typeid the following applies:

When applied to an expression of polymorphic type, evaluation of a typeid expression may involve runtime overhead (a virtual table lookup), otherwise typeid expression is resolved at compile time.

Thus, I wouldn't consider typeid a good option for static (compile time) polymorphism.

One way you could solve your problem is by using tag dispatching in combination with a delegating costructor, as follows:

template <typename T>
class A{
  std::vector<T> v;

  template <typename InputIterator>
  A(InputIterator first, InputIterator last, std::input_iterator_tag) : v(first, last) {}

public:

  template<typename InputIterator> A(InputIterator first, InputIterator last)
  : A(first, last, typename std::iterator_traits<InputIterator>::iterator_category()) {}
};

Live Demo

like image 135
101010 Avatar answered Oct 21 '22 19:10

101010


The use of the operator typeid() is because I find it really easy to read when mantaining the code, but I can't get it to work in any way.

typeid is a facility mainly used to query type information at runtime. While you might find it more "readable", it's not the right tool for the job and it will confuse every other C++ developer.


Other solutions look very weird and are really obscure (like forcing the InputIterator parameter to have certain methods such as *it or ++it)

I strongly advise you to reconsider this. An InputIterator is a concept that describers valid operations on a type. The whole idea behind a concept is checking for operation validity - there is nothing more than InputIterator than its requirements.

The correct and idiomatic way of solving your issue is creating a constexpr bool is_input_iterator<T> variable/function that returns true if T matches InputIterator's requirements. You can easily implement that using the detection idiom... and it doesn't get more readable than std::enable_if_t<is_input_iterator<T>>.

Here a simplified example (I'm not checking for all the requirements):

template <typename T>
using supports_increment = decltype(++std::declval<T>());

template <typename T>
using supports_dereference = decltype(*std::declval<T>());

template <typename T>
constexpr bool is_input_iterator = 
    std::experimental::is_detected_v<supports_increment, T> 
 && std::experimental::is_detected_v<supports_dereference, T>;

  template <typename InputIterator,
           typename = std::enable_if_t<is_input_iterator<InputIterator>>>
  A(InputIterator first, InputIterator last) : v(first, last) {}

live example on wandbox

like image 44
Vittorio Romeo Avatar answered Oct 21 '22 20:10

Vittorio Romeo