c++templatesc++17sfinaeclass-template

Compilation error when creating SFINAE based constructor


During the implementation of a class that has SFINAE based ctor, I get the next error: In template: no type named 'type' in 'std::enable_if<false, void *>'; 'enable_if' cannot be used to disable this declaration

The SFINAE is based on previous template which is been worked on before.

The code is:

#include <iostream>
#include <type_traits>

namespace infra {
namespace io {
struct IoDataFrameFactory {};
struct IoDataFrame {};
}


}

template<typename T1, typename T2>
class IoDataFramePair : public infra::io::IoDataFrame {
 public:
  IoDataFramePair(std::istream &is, const infra::io::IoDataFrameFactory &factory,
                  typename std::enable_if_t<(std::is_constructible_v<T1, std::istream &, const infra::io::IoDataFrameFactory &> &&
                      std::is_constructible_v<T2, std::istream &, const infra::io::IoDataFrameFactory &>), void *> = nullptr)
      : first(is, factory), second(is, factory)
  {
      checkSuffix(is);
  }
  
  IoDataFramePair(std::istream &is, const infra::io::IoDataFrameFactory &factory,
                  typename std::enable_if_t<(!std::is_constructible_v<T1, std::istream &, const infra::io::IoDataFrameFactory &>
                      &&
                          std::is_constructible_v<T2, std::istream &, const infra::io::IoDataFrameFactory &>), void *> = nullptr)
      : first(is), second(is, factory)
  {
      checkSuffix(is);
  }
  
  IoDataFramePair(std::istream &is, const infra::io::IoDataFrameFactory &factory,
                  typename std::enable_if_t<(std::is_constructible_v<T1, std::istream &, const infra::io::IoDataFrameFactory &> &&
                      !std::is_constructible_v<T2, std::istream &, const infra::io::IoDataFrameFactory &>), void *> = nullptr)
      : first(is, factory), second(is)
  {
      checkSuffix(is);
  }
  
  IoDataFramePair(std::istream &is, const infra::io::IoDataFrameFactory &factory,
                  typename std::enable_if_t<(!std::is_constructible_v<T1, std::istream &, const infra::io::IoDataFrameFactory &>
                      &&
                          !std::is_constructible_v<T2, std::istream &, const infra::io::IoDataFrameFactory &>), void *> = nullptr)
      : first(is), second(is)
  {
      checkSuffix(is);
  }
 
 private:
  int m_version;
  // Define other members and functions as needed
  
  void checkSuffix(std::istream &is)
  {
      // Implement checkSuffix logic
  }
  
  std::istream m_beginIS;
  std::istream m_middleIs;
  T1 first;
  T2 second;
};

int main()
{
    // Usage example
    std::stringstream is();  // You need to provide an actual input stream
    infra::io::IoDataFrameFactory factory;  // You need to provide an actual IoDataFrameFactory
    
    IoDataFramePair<int, double> pair1(is, factory);  // Calls the appropriate constructor
    IoDataFramePair<int, int> pair2(is, factory);     // Calls the appropriate constructor
    // Similar usage for other cases
    return 0;
}

What am I doing wrong and how to fix it?


Solution

  • You need to make the types you test dependent types. Here are two ways adding U1 and U2 that defaults to T1 and T2.

    1. Not adding an argument to the function:
    template <class U1 = T1, class U2 = T2, std::enable_if_t<
            (std::is_constructible_v<U1, std::istream&,
                                        const infra::io::IoDataFrameFactory&> &&
                std::is_constructible_v<U2, std::istream&,
                                        const infra::io::IoDataFrameFactory&>)>* =
            nullptr>
    IoDataFramePair(std::istream& is, const infra::io::IoDataFrameFactory& factory)
        : first(is, factory), second(is, factory) {
        checkSuffix(is);
    }
    
    1. Still adding an argument to the function:
    template<class U1 = T1, class U2 = T2>
    IoDataFramePair(
        std::istream& is, const infra::io::IoDataFrameFactory& factory,
        std::enable_if_t<
            (!std::is_constructible_v<U1, std::istream&,
                                        const infra::io::IoDataFrameFactory&> &&
                std::is_constructible_v<U2, std::istream&,
                                        const infra::io::IoDataFrameFactory&>)>* =
            nullptr)
        : first(is), second(is, factory) {
        checkSuffix(is);
    }
    

    ... and do the same for the other two constructors.

    Demo - Note: I prefer to keep the extra argument out of the constructors (version 1 above) so I used that approach in all constructors in the demo.


    Note: You have other problems, like trying to default construct istreams and trying to initialize and int and a double with an istream, but I think that's beside the point.