예쁜 인쇄 std :: tuple p) {

이것은 예쁘게 인쇄되는 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, 쌍 구분 기호)을 포함하면 보너스 포인트!



답변

예, 지수 ~

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)...};
}

답변

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


답변

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;
}

답변

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)

라이브 데모


답변

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)

답변

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

답변

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();
}