Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confused by default constructor description of std::tuple in the ISO C++ Standard

The Standard says that std::tuple has the following member functions

constexpr tuple();
explicit tuple(const Types&...);

Can someone please explain what is supposed to happen for std::tuple<>?

like image 212
Johannes Schaub - litb Avatar asked Jul 08 '12 19:07

Johannes Schaub - litb


People also ask

What are the default constructors in C++?

A default constructor is a constructor that either has no parameters, or if it has parameters, all the parameters have default values. If no user-defined constructor exists for a class A and one is needed, the compiler implicitly declares a default parameterless constructor A::A() .

What is std :: tuple?

Class template std::tuple is a fixed-size collection of heterogeneous values. It is a generalization of std::pair. If std::is_trivially_destructible<Ti>::value is true for every Ti in Types , the destructor of tuple is trivial.

Can a default constructor be defined?

A default constructor is a constructor which can be called with no arguments (either defined with an empty parameter list, or with default arguments provided for every parameter). A type with a public default constructor is DefaultConstructible.

Is std :: tuple a container?

The new std::array and std::tuple containers provide developers with additional ways to manage structured data efficiently.


2 Answers

I guess the definition given in the standard is supposed to be pseudocode. That is the case with many of the definitions in the standard; it contains several requirements that are given verbally, but are satisfiable only with tricks like enable_if. This seems to be an example where the C++-like pseudocode notation can actually lead to illegal C++ when trying to instantiate such an empty tuple (or it might just be an omission).

Both stdlibc++ and libc++ have an explicit specialization for the zero-element tuple. For example, in stdlibc++:

  // Explicit specialization, zero-element tuple.
  template<>  
    class tuple<>
    {
    public:
      void swap(tuple&) noexcept { /* no-op */ }
    };

with an implicitly-defined unambiguous default constructor.

Libc++ does not explicitly declare the parameterless default constructor. Presumably the templated constructor is then chosen as default constructor for non-empty tuples.

Interestingly, the two libraries disagree on what members the empty tuple has. For example, the following compiles with libc++, but not with libstdc++:

#include <tuple>
#include <memory>

int main() {
  std::tuple<> t(std::allocator_arg, std::allocator<int>());
}
like image 177
Philipp Avatar answered Sep 19 '22 19:09

Philipp


I believe this is a minor error in the standard. Clearly, when the Types parameter pack is empty, the two constructor calls are equivalent and cannot be overloaded (see C++11 section 13). (Further note that the constructor using Types is not a member template either --if it was, then it would be a legal overload.).

In other words, this code will not compile:

template <typename... Types>
struct Test
{
  constexpr Test() {}
  explicit Test(Types const&...) { /* etc. */ }
};

int main()
{
  Test<> a;
  Test<int> b;
}

e.g., a g++ v4.8 snapshot outputs:

tt.cxx: In instantiation of ‘struct Test<>’:
tt.cxx:10:10:   required from here
tt.cxx:5:12: error: ‘Test<Types>::Test(const Types& ...) [with Types = {}]’ cannot be overloaded
   explicit Test(Types const&...) { /* etc. */ }
            ^
tt.cxx:4:13: error: with ‘constexpr Test<Types>::Test() [with Types = {}]’
   constexpr Test() {}
             ^

This can be fixed by using partial specialization:

template <typename... Types>
struct Test
{
  constexpr Test() {} // default construct all elements
  explicit Test(Types const&...) { /* etc. */ }
  // and all other member definitions
};

template <>
struct Test<>
{
  constexpr Test() {}
  // and any other member definitions that make sense with no types
};

int main()
{
  Test<> a;
  Test<int> b;
}

which will compile correctly.

It appears the standard wanted a constexpr default constructor was so that std::tuple<> var; could be written instead of writing std::tuple<> var(); or std::tuple<> var{}; because of the use of explicit with the other constructor. Unfortunately, its definition of std::tuple does not work for tuples of size zero. The standard does permit such in section 20.4.2.7 (relational operators) though, "For any two zero-length tuples, [...]". Oops! :-)

like image 39
Paul Preney Avatar answered Sep 20 '22 19:09

Paul Preney