1

I am trying to implement a templated pair class that uses sfinae to distinguish between array and non-array types. So far, I have the following code:

template <typename T>
class pair
{
public:

   //default constructors. one for non-array types and one for arrays. These work.
   template <typename temp_type = T>
   pair(typename boost::disable_if<boost::is_array<temp_type>>::type* ignore = 0)
      : first(T())
      , second(T())
   {}

   template <typename temp_type = T>
   pair(typename boost::enable_if<boost::is_array<temp_type>>::type* ignore = 0)
   {}

   //assignment operator attempts for non-array types (not in code at same time.) These don't work.
   template<typename temp_type = T> 
   pair<temp_type>& operator=(pair<typename boost::disable_if<boost::is_array<temp_type>>::type>                const& rhs)
   {
       this->first = rhs.first;
       this->second = rhs.second;
       return *this;
   }

   template<typename temp_type = T> 
   auto operator=(pair<temp_type> const& rhs) -> pair<typename boost::disable_if<boost::is_array<temp_type>>::type>&
   {
       this->first = rhs.first;
       this->second = rhs.second;
       return *this;
   }

   T first;
   T second;
};

The first attempt at the assignment operator fails with an "illegal use of type void" error. The second compiles, but when I debug, MSVC tells me that "no executable code is associated with that line." I am using the MSVC 12 64 bit compiler.

If you could offer any insight as to what is wrong here, that would be very helpful.

Also, just a few observations I've made about sfinae in c++ (that may be right or wrong):

  • sfinae requires the member function to be templated but it cannot use the class template type(s) for this purpose. Why?
  • sfinae can be done by only modifying the template parameters and not the return type or inputs. How, exactly does this work. What's the flexibility of this method?

I know this is long winded and references topics covered in numerous other posts, but I haven't been able to put those explanations together in a way that concisely explains sfinae in c++.

Some posts I've read:

Explain C++ SFINAE to a non-C++ programmer (high level intro)

Select class constructor using enable_if (sfinae for constructors)

Thanks for any help.

EDIT:

I have modified my code based on the comments below and I still can't get this to work as expected (The compiler is not even seeing the source.) It's an interesting situation because the example is completely contrived and the default assignment operator actually works in this scenario. Nevertheless, I don't think the compiler should be overriding my attempt to overload the operator.

I have tried the following 4 methods and none of them seem to be built into the executable:

template<typename temp_type = T> 
pair<temp_type>& operator=(pair<typename boost::disable_if<boost::is_array<temp_type>, temp_type>::type> const& rhs)

{
        this->first = rhs.first;
        this->second = rhs.second;
        return *this;
}

template<typename temp_type = T>
auto operator=(pair<temp_type> const& rhs) -> pair<typename boost::disable_if<boost::is_array<temp_type>, temp_type>::type>&
{
    this->first = rhs.first;
    this->second = rhs.second;
    return *this;
}

template<typename ret_type = boost::disable_if<boost::is_array<T>, T>::type, typename = void>
pair<ret_type>& operator=(pair<ret_type> const& rhs)
{
    this->first = rhs.first;
    this->second = rhs.second;
    return *this;
}

template<typename temp_type = T, typename boost::enable_if<boost::is_array<temp_type>, temp_type>::type = 0>
pair<T>& operator=(pair<temp_type> const& rhs)
{
    this->first = rhs.first;
    this->second = rhs.second;
    return *this;
}

Thoughts?

Community
  • 1
  • 1
mgoldman
  • 169
  • 11

2 Answers2

1

Don't forget, converting assignment can be implemented by template functions, but copy and move assignment operators can't.

A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&.

If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy assignment operator is defined as deleted; otherwise, it is defined as defaulted

A user-declared move assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X&&, const X&&, volatile X&&, or const volatile X&&.

and the note

Because a template assignment operator or an assignment operator taking an rvalue reference parameter is never a copy assignment operator, the presence of such an assignment operator does not suppress the implicit declaration of a copy assignment operator. Such assignment operators participate in overload resolution with other assignment operators, including copy assignment operators, and, if selected, will be used to assign an object.

(above quotes found in section 12.8, wording from draft n3936)

Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks Ben. Can you clarify how the compiler determines which overloaded function to call. I may be misinterpreting the std, but it seems like my assignment operator should still be considered in the overload set? If so, any ideas as to why it is not being chosen? – mgoldman Oct 10 '14 at 19:15
  • @mgoldman: All other things equal, a non-template is chosen before a template function. 13.3.3. has a rule about "a viable function F1 is defined to be a better function than another viable function F2 if .... F1 is not a function template specialization and F2 is a function template specialization" – Ben Voigt Oct 10 '14 at 19:17
1

Why your attempts fail to work is explained in @BenVoigt 's answer, here is only an alternative solution. You can dispatch the operator= call to an appropriate overload depending on the type your template was instantiated with:

#include <iostream>
#include <boost/type_traits.hpp>

template <typename T>
class pair
{    
public:    
    pair& operator=(pair const& rhs)
    {
        return assign(rhs, boost::is_array<T>());
    }

private:
    pair& assign(pair const&, boost::true_type)
    {
        std::cout << "array" << std::endl;
        return *this;
    }

    pair& assign(pair const&, boost::false_type)
    {
        std::cout << "not array" << std::endl;
        return *this;
    }
};

int main()
{
    pair<int> a, b;
    b = a;

    pair<int[]> c, d;
    c = d;
}

Output:

not array
array

And you can do the same with constructors, delegating (C++11) the call to another one:

pair() : pair(boost::is_array<T>()) {}

pair(boost::true_type) { /*initialize array pair*/ }

pair(boost::false_type) { /*initialize non-array pair*/ }

Code looks cleaner and you don't have to compete with the compiler on whose operator= better matches the actual argument.

DEMO

Community
  • 1
  • 1
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160