18

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<>?

7
  • 3
    std::tuple<> isn't an expression. what's the question? Commented Jul 8, 2012 at 19:39
  • 4
    @Cheersandhth.-Alf why should it be an expression. It's a class type. The constructor declarations will conflict and raise an error. How is this solved? Commented Jul 8, 2012 at 19:42
  • 4
    In other words, it would seem that the default constructor is ambiguous? Commented Jul 8, 2012 at 19:47
  • 1
    @johannes: post this as a defect to [comp.std.c++], pluh-ease. Commented Jul 8, 2012 at 20:02
  • 6
    There's two relevant open cwg issues: open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1395 and open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1432 Commented Jul 8, 2012 at 20:19

3 Answers 3

6

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>()); } 
Sign up to request clarification or add additional context in comments.

12 Comments

I've tried to find it but was not successful. Can you please point me to its specification?
One would expect that in any real implementation, but I don't see it in the Holy Standard.
I'm not sure I agree with your edit. The standard's tuple class declaration is perfectly valid C++, so long as it is not instantiated with an empty template argument list, is it not?
Another standard quote (§20.4.2.7/5): "For any two zero-length tuples e and f, e < f returns false."
@hvd: Actually, there is a reference to tuple<>() in a rather obscure (for me) example of std::scoped_allocator_traits::construct() at § 20.12.4-12. Anyway, I would expect it added in a future revision of the library, although I cannot find any defect report in the C++ Standard Library Issues List.
|
3

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! :-)

1 Comment

lol, who downvoted this? it's correct by my understanding of this accepted paper: open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4387 see Discussion, point 4 - which cites this very thread and Johannes by name.
-1

At first sight, the ambiguity would only matter at the point where it's called, and then you have normal overload resolution.

5 Comments

The first sight is incorrect. Declaring a default constructor with zero parameters two times in a class is invalid. Even if it somehow worked, it would be ambiguous in overload resolution :)
@JohannesSchaub-litb: In normal classes. Templates are another matter, as members of a class template are only instantiated as needed. In general the compiler can't easily determine whether two methods of a template would be ambiguous before instantiation.
... and while I was trying to figure out the exact overload resolution rules for parameter packs, Howard Hinnant listed the open issues on them.
No, that is not true. The declaration of all memberfunctions and all datamembers are instantiated when the enclosing class template is.
Ah yes, the declarations are. (I'm assuming implicit instantiation, 14.7.1 - your question is a bit short on code)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.