예쁜 인쇄 std :: tuple


89

이것은 예쁘게 인쇄되는 STL 컨테이너 에 대한 이전 질문에 대한 후속 조치로 , 매우 우아하고 완전히 일반적인 솔루션을 개발할 수있었습니다.


이 다음 단계에서는 std::tuple<Args...>가변 템플릿을 사용하여에 대한 pretty-printing을 포함하고 싶습니다 (따라서 이것은 엄격하게 C ++ 11입니다). 의 경우 std::pair<S,T>간단히

std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p)
{
  return o << "(" << p.first << ", " << p.second << ")";
}

튜플을 인쇄하는 유사한 구조는 무엇입니까?

다양한 템플릿 인수 스택 압축을 풀고 인덱스를 전달하고 SFINAE를 사용하여 마지막 요소에 있는지 확인했지만 성공하지 못했습니다. 깨진 코드로 당신에게 부담을주지 않습니다. 문제 설명은 충분히 간단합니다. 기본적으로 다음과 같은 동작을 원합니다.

auto a = std::make_tuple(5, "Hello", -0.1);
std::cout << a << std::endl; // prints: (5, "Hello", -0.1)

이전 질문과 동일한 수준의 일반성 (char / wchar_t, 쌍 구분 기호)을 포함하면 보너스 포인트!


누군가 여기에 코드를 라이브러리에 넣었습니까? 아니면 .hpp-with-everything-을 잡고 사용할 수 있습니까?
einpoklum

@einpoklum : 어쩌면 cxx-prettyprint ? 그게 제가 그 코드를 필요로했던 것입니다.
Kerrek SB

1
좋은 질문입니다. "내가 깨진 코드로 당신에게 부담을주지 않을 것입니다"에 대한 +1입니다. 놀랍지 만, 실제로 무의미한 "당신이 시도한"무리를 막는 데 성공한 것 같습니다.
Don Hatch

답변:


78

예, 지수 ~

namespace aux{
template<std::size_t...> struct seq{};

template<std::size_t N, std::size_t... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};

template<std::size_t... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple(std::basic_ostream<Ch,Tr>& os, Tuple const& t, seq<Is...>){
  using swallow = int[];
  (void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...};
}
} // aux::

template<class Ch, class Tr, class... Args>
auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t)
    -> std::basic_ostream<Ch, Tr>&
{
  os << "(";
  aux::print_tuple(os, t, aux::gen_seq<sizeof...(Args)>());
  return os << ")";
}

Ideone의 라이브 예.


구분 기호의 경우 다음 부분 전문화를 추가하십시오.

// Delimiters for tuple
template<class... Args>
struct delimiters<std::tuple<Args...>, char> {
  static const delimiters_values<char> values;
};

template<class... Args>
const delimiters_values<char> delimiters<std::tuple<Args...>, char>::values = { "(", ", ", ")" };

template<class... Args>
struct delimiters<std::tuple<Args...>, wchar_t> {
  static const delimiters_values<wchar_t> values;
};

template<class... Args>
const delimiters_values<wchar_t> delimiters<std::tuple<Args...>, wchar_t>::values = { L"(", L", ", L")" };

그리고 변경 operator<<print_tuple그에을 :

template<class Ch, class Tr, class... Args>
auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t)
    -> std::basic_ostream<Ch, Tr>&
{
  typedef std::tuple<Args...> tuple_t;
  if(delimiters<tuple_t, Ch>::values.prefix != 0)
    os << delimiters<tuple_t,char>::values.prefix;

  print_tuple(os, t, aux::gen_seq<sizeof...(Args)>());

  if(delimiters<tuple_t, Ch>::values.postfix != 0)
    os << delimiters<tuple_t,char>::values.postfix;

  return os;
}

template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple(std::basic_ostream<Ch, Tr>& os, Tuple const& t, seq<Is...>){
  using swallow = int[];
  char const* delim = delimiters<Tuple, Ch>::values.delimiter;
  if(!delim) delim = "";
  (void)swallow{0, (void(os << (Is == 0? "" : delim) << std::get<Is>(t)), 0)...};
}

@Kerrek : 현재 테스트 및 수정 중이지만 Ideone에서 이상한 출력을 얻습니다.
Xeo

나는 당신이 또한 스트림과 문자열을 혼동하고 있다고 생각합니다. "std :: cout << std :: cout"과 유사한 것을 작성하고 있습니다. 즉, TuplePrinter이 없습니다 operator<<.
Kerrek SB 2011 년

1
@Thomas : 그냥 사용할 수 없습니다 class Tuple에 대한 operator<<과부하 - 그것은 어떤 모든 것들에 대한 발탁 것이다. 어떤 종류의 가변 인자가 필요하다는 것을 암시하는 제약이 필요합니다.
Xeo

1
@DanielFrey : 해결 된 문제입니다. 목록 초기화는 왼쪽에서 오른쪽 순서를 보장 swallow{(os << get<Is>(t))...};합니다..
Xeo 2013 년

6
@Xeo 나는에 대한 귀하의 제비를 빌려 cppreference 당신이 괜찮다면,.
Cubbi

23

C ++ 17에서는 Fold 표현식 , 특히 단항 왼쪽 폴드 를 활용하여 코드를 조금 더 적게 사용하여이를 수행 할 수 있습니다 .

template<class TupType, size_t... I>
void print(const TupType& _tup, std::index_sequence<I...>)
{
    std::cout << "(";
    (..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
    std::cout << ")\n";
}

template<class... T>
void print (const std::tuple<T...>& _tup)
{
    print(_tup, std::make_index_sequence<sizeof...(T)>());
}

라이브 데모 출력 :

(5, 안녕하세요, -0.1)

주어진

auto a = std::make_tuple(5, "Hello", -0.1);
print(a);

설명

우리의 단항 왼쪽 폴드는

... op pack

여기서 op우리의 시나리오에서하면 콤마 연산자와 pack같은 팽창 상황에서 우리 튜플을 포함하는 식이다 :

(..., (std::cout << std::get<I>(myTuple))

그래서 다음과 같은 튜플이 있다면 :

auto myTuple = std::make_tuple(5, "Hello", -0.1);

그리고 std::integer_sequence그 값이 유형이 아닌 템플릿으로 지정된 값 (위 코드 참조)

size_t... I

그런 다음 표현

(..., (std::cout << std::get<I>(myTuple))

확장됩니다

((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple));

어느 것이 인쇄됩니다

5Hello-0.1

끔찍하기 때문에 첫 번째 요소가 아닌 한 먼저 인쇄 할 쉼표 구분 기호를 추가하려면 더 많은 트릭을 수행해야합니다.

해당 작업을 수행하기 위해, 우리는 수정 pack인쇄 스크롤 식의 일부를 " ,"현재의 인덱스는 경우 I, 제 아니다 따라서 (I == 0? "" : ", ")* :

(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));

그리고 이제 우리는

5, 안녕하세요, -0.1

더 멋지게 보입니다 (참고 : 이 답변 과 비슷한 출력을 원했습니다 )

* 참고 : 쉼표 구분은 제가 끝낸 것보다 다양한 방법으로 할 수 있습니다. 처음에는에 대해 테스트하여 이전 이 아닌 뒤에 조건부로 쉼표를 추가 했지만 너무 길어서 대신에 대해 테스트 했지만 결국 Xeo를 복사 하여 얻은 결과를 얻었습니다.std::tuple_size<TupType>::value - 1sizeof...(I) - 1


1
if constexpr기본 케이스 에도 사용할 수 있습니다 .
Kerrek SB

@KerrekSB : 쉼표 인쇄 여부를 결정하기 위해? 나쁜 생각은 아니에요. 삼항으로 나오길 바랍니다.
AndyG

조건식은 이미 잠재적 인 상수 표현식이므로 이미 가지고있는 것이 좋습니다. :-)
Kerrek SB dec

19

C ++ 11 (gcc 4.7)에서 잘 작동합니다. 내가 고려하지 않은 몇 가지 함정이 있다고 확신하지만 코드는 읽기 쉽고 복잡하지 않다고 생각합니다. 이상 할 수있는 유일한 것은 마지막 요소에 도달했을 때 종료되도록하는 "guard"구조체 tuple_printer입니다. 또 다른 이상한 점은 Types 유형 팩의 유형 수를 반환하는 sizeof ... (Types) 일 수 있습니다. 마지막 요소의 인덱스를 결정하는 데 사용됩니다 (크기 ... (유형)-1).

template<typename Type, unsigned N, unsigned Last>
struct tuple_printer {

    static void print(std::ostream& out, const Type& value) {
        out << std::get<N>(value) << ", ";
        tuple_printer<Type, N + 1, Last>::print(out, value);
    }
};

template<typename Type, unsigned N>
struct tuple_printer<Type, N, N> {

    static void print(std::ostream& out, const Type& value) {
        out << std::get<N>(value);
    }

};

template<typename... Types>
std::ostream& operator<<(std::ostream& out, const std::tuple<Types...>& value) {
    out << "(";
    tuple_printer<std::tuple<Types...>, 0, sizeof...(Types) - 1>::print(out, value);
    out << ")";
    return out;
}

1
예, 그것은 합리적으로 보입니다-아마도 완전성을 위해 빈 튜플에 대한 또 다른 전문화를 통해.
Kerrek SB 2013

@KerrekSB, C ++에서 튜플을 인쇄하는 간단한 방법이 없습니까?, python 함수에서 암시 적으로 튜플을 반환하고 std::make_tuple(). 그러나에서 인쇄 할 main()때 많은 오류가 발생합니다!, 튜플을 인쇄하는 더 간단한 방법에 대한 제안이 있습니까?
Anu

17

cppreference에 대한 구현 이 아직 여기에 게시되지 않은 것에 놀랐습니다. 그래서 후손을 위해 할 것입니다. 문서에 숨겨져 std::tuple_cat있으므로 찾기가 쉽지 않습니다. 다른 솔루션과 마찬가지로 가드 구조체를 사용하지만 궁극적으로 더 간단하고 따르기 쉽다고 생각합니다.

#include <iostream>
#include <tuple>
#include <string>

// helper function to print a tuple of any size
template<class Tuple, std::size_t N>
struct TuplePrinter {
    static void print(const Tuple& t) 
    {
        TuplePrinter<Tuple, N-1>::print(t);
        std::cout << ", " << std::get<N-1>(t);
    }
};

template<class Tuple>
struct TuplePrinter<Tuple, 1> {
    static void print(const Tuple& t) 
    {
        std::cout << std::get<0>(t);
    }
};

template<class... Args>
void print(const std::tuple<Args...>& t) 
{
    std::cout << "(";
    TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
    std::cout << ")\n";
}
// end helper function

그리고 테스트 :

int main()
{
    std::tuple<int, std::string, float> t1(10, "Test", 3.14);
    int n = 7;
    auto t2 = std::tuple_cat(t1, std::make_pair("Foo", "bar"), t1, std::tie(n));
    n = 10;
    print(t2);
}

산출:

(10, 테스트, 3.14, Foo, 바, 10, 테스트, 3.14, 10)

라이브 데모


4

AndyG 코드 기반, C ++ 17 용

#include <iostream>
#include <tuple>

template<class TupType, size_t... I>
std::ostream& tuple_print(std::ostream& os,
                          const TupType& _tup, std::index_sequence<I...>)
{
    os << "(";
    (..., (os << (I == 0 ? "" : ", ") << std::get<I>(_tup)));
    os << ")";
    return os;
}

template<class... T>
std::ostream& operator<< (std::ostream& os, const std::tuple<T...>& _tup)
{
    return tuple_print(os, _tup, std::make_index_sequence<sizeof...(T)>());
}

int main()
{
    std::cout << "deep tuple: " << std::make_tuple("Hello",
                  0.1, std::make_tuple(1,2,3,"four",5.5), 'Z')
              << std::endl;
    return 0;
}

출력 포함 :

deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z)

3

Bjarne Stroustrup의 C ++ 프로그래밍 언어 (817 페이지)대한 예제를 기반으로합니다 .

#include <tuple>
#include <iostream>
#include <string>
#include <type_traits>
template<size_t N>
struct print_tuple{
    template<typename... T>static typename std::enable_if<(N<sizeof...(T))>::type
    print(std::ostream& os, const std::tuple<T...>& t) {
        char quote = (std::is_convertible<decltype(std::get<N>(t)), std::string>::value) ? '"' : 0;
        os << ", " << quote << std::get<N>(t) << quote;
        print_tuple<N+1>::print(os,t);
        }
    template<typename... T>static typename std::enable_if<!(N<sizeof...(T))>::type
    print(std::ostream&, const std::tuple<T...>&) {
        }
    };
std::ostream& operator<< (std::ostream& os, const std::tuple<>&) {
    return os << "()";
    }
template<typename T0, typename ...T> std::ostream&
operator<<(std::ostream& os, const std::tuple<T0, T...>& t){
    char quote = (std::is_convertible<T0, std::string>::value) ? '"' : 0;
    os << '(' << quote << std::get<0>(t) << quote;
    print_tuple<1>::print(os,t);
    return os << ')';
    }

int main(){
    std::tuple<> a;
    auto b = std::make_tuple("One meatball");
    std::tuple<int,double,std::string> c(1,1.2,"Tail!");
    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << c << std::endl;
    }

산출:

()
("One meatball")
(1, 1.2, "Tail!")

3

std::apply(C ++ 17)을 활용 std::index_sequence하여 단일 함수를 삭제 하고 정의 할 수 있습니다 .

#include <tuple>
#include <iostream>

template<class Ch, class Tr, class... Args>
auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) {
  std::apply([&os](auto&&... args) {((os << args << " "), ...);}, t);
  return os;
}

또는 stringstream의 도움으로 약간 장식되었습니다.

#include <tuple>
#include <iostream>
#include <sstream>

template<class Ch, class Tr, class... Args>
auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) {
  std::basic_stringstream<Ch, Tr> ss;
  ss << "[ ";
  std::apply([&ss](auto&&... args) {((ss << args << ", "), ...);}, t);
  ss.seekp(-2, ss.cur);
  ss << " ]";
  return os << ss.str();
}

1

@Kerrek SB가 제안한 빈 튜플에 대한 전문화를 포함하여 @Tony Olsson과 유사한 또 다른 하나입니다.

#include <tuple>
#include <iostream>

template<class Ch, class Tr, size_t I, typename... TS>
struct tuple_printer
{
    static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
    {
        tuple_printer<Ch, Tr, I-1, TS...>::print(out, t);
        if (I < sizeof...(TS))
            out << ",";
        out << std::get<I>(t);
    }
};
template<class Ch, class Tr, typename... TS>
struct tuple_printer<Ch, Tr, 0, TS...>
{
    static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
    {
        out << std::get<0>(t);
    }
};
template<class Ch, class Tr, typename... TS>
struct tuple_printer<Ch, Tr, -1, TS...>
{
    static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
    {}
};
template<class Ch, class Tr, typename... TS>
std::ostream & operator<<(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
{
    out << "(";
    tuple_printer<Ch, Tr, sizeof...(TS) - 1, TS...>::print(out, t);
    return out << ")";
}

0

나는 DarioP의 대답을 좋아하지만 stringstream은 힙을 사용합니다. 이것은 피할 수 있습니다.

template <class... Args>
std::ostream& operator<<(std::ostream& os, std::tuple<Args...> const& t) {
  os << "(";
  bool first = true;
  std::apply([&os, &first](auto&&... args) {
    auto print = [&] (auto&& val) {
      if (!first)
        os << ",";
      (os << " " << val);
      first = false;
    };
    (print(args), ...);
  }, t);
  os << " )";
  return os;
}

0

폴드 식을 사용하는 이전 답변에 대해 내가 싫어하는 한 가지는 인덱스 시퀀스 또는 플래그를 사용하여 첫 번째 요소를 추적하여 멋진 클린 폴드 식의 많은 이점을 제거한다는 것입니다.

다음은 인덱싱이 필요하지 않지만 유사한 결과를 얻는 예입니다. (다른 것만 큼 정교하지는 않지만 더 추가 할 수 있습니다.)

이 기술은 접기가 이미 제공하는 것을 사용하는 것입니다. 하나의 요소에 대한 특별한 경우입니다. 즉, 하나 개의 요소가 배에 불과가 확장 elem[0]된다 후 2 개 요소 elem[0] + elem[1], 여기서 +일부 동작이다. 우리가 원하는 것은 한 요소가 해당 요소 만 스트림에 쓰고 더 많은 요소에 대해 동일한 작업을 수행하지만 각 요소를 ","의 추가 쓰기로 결합하는 것입니다. 따라서 이것을 C ++ 폴드에 매핑하면 각 요소가 일부 개체를 스트림에 쓰는 작업이되기를 원합니다. 우리는 +작업이 ","쓰기를 사용하여 두 개의 쓰기를 산재하기를 원합니다 . 그래서 먼저 우리의 튜플 시퀀스를 쓰기 동작의 시퀀스로 변환하고 CommaJoiner, 그 operator+동작에 대해 우리가 원하는 방식으로 두 동작을 결합하기 위해를 추가하고 그 사이에 ","를 추가합니다.

#include <tuple>
#include <iostream>

template <typename T>
struct CommaJoiner
{
    T thunk;
    explicit CommaJoiner(const T& t) : thunk(t) {}

    template <typename S>
    auto operator+(CommaJoiner<S> const& b) const
    {
        auto joinedThunk = [a=this->thunk, b=b.thunk] (std::ostream& os) {
            a(os);
            os << ", ";
            b(os);
        };
        return CommaJoiner<decltype(joinedThunk)>{joinedThunk};
    }

    void operator()(std::ostream& os) const
    {
        thunk(os);
    }

};

template <typename ...Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> tup)
{
    std::apply([&](auto ...ts) {
        return (... + CommaJoiner{[=](auto&os) {os << ts;}});}, tup)(os);

    return os;
}

int main() {
    auto tup = std::make_tuple(1, 2.0, "Hello");
    std::cout << tup << std::endl;
}

godbolt를 훑어 보면 이것이 잘 컴파일되고 모든 썽크 호출이 평평 해짐을 알 수 있습니다.

그러나 빈 튜플을 처리하려면 두 번째 오버로드가 필요합니다.


0

다음은 최근에 튜플을 인쇄하기 위해 만든 코드입니다.

#include <iostream>
#include <tuple>

using namespace std;

template<typename... Ts>
ostream& operator<<(ostream& output, const tuple<Ts...> t) {
    output << '(';
    apply([&](auto&&... args) {
        ((cout << args << ", "), ...);
    }, t);
    output << "\b\b";
    output << ')';
    return output;
}

예제 사례 사용 :

auto a = std::make_tuple(5, "Hello", -0.1); 
cout << a << '\n'; // (5, Hello, -0.1)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.