323 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
		
		
			
		
	
	
			323 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
| 
								 | 
							
								#pragma once
							 | 
						||
| 
								 | 
							
								/*
							 | 
						||
| 
								 | 
							
								    tests/constructor_stats.h -- framework for printing and tracking object
							 | 
						||
| 
								 | 
							
								    instance lifetimes in example/test code.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    All rights reserved. Use of this source code is governed by a
							 | 
						||
| 
								 | 
							
								    BSD-style license that can be found in the LICENSE file.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This header provides a few useful tools for writing examples or tests that want to check and/or
							 | 
						||
| 
								 | 
							
								display object instance lifetimes.  It requires that you include this header and add the following
							 | 
						||
| 
								 | 
							
								function calls to constructors:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    class MyClass {
							 | 
						||
| 
								 | 
							
								        MyClass() { ...; print_default_created(this); }
							 | 
						||
| 
								 | 
							
								        ~MyClass() { ...; print_destroyed(this); }
							 | 
						||
| 
								 | 
							
								        MyClass(const MyClass &c) { ...; print_copy_created(this); }
							 | 
						||
| 
								 | 
							
								        MyClass(MyClass &&c) { ...; print_move_created(this); }
							 | 
						||
| 
								 | 
							
								        MyClass(int a, int b) { ...; print_created(this, a, b); }
							 | 
						||
| 
								 | 
							
								        MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); }
							 | 
						||
| 
								 | 
							
								        MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        ...
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								You can find various examples of these in several of the existing testing .cpp files.  (Of course
							 | 
						||
| 
								 | 
							
								you don't need to add any of the above constructors/operators that you don't actually have, except
							 | 
						||
| 
								 | 
							
								for the destructor).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Each of these will print an appropriate message such as:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ### MyClass @ 0x2801910 created via default constructor
							 | 
						||
| 
								 | 
							
								    ### MyClass @ 0x27fa780 created 100 200
							 | 
						||
| 
								 | 
							
								    ### MyClass @ 0x2801910 destroyed
							 | 
						||
| 
								 | 
							
								    ### MyClass @ 0x27fa780 destroyed
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								You can also include extra arguments (such as the 100, 200 in the output above, coming from the
							 | 
						||
| 
								 | 
							
								value constructor) for all of the above methods which will be included in the output.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								For testing, each of these also keeps track the created instances and allows you to check how many
							 | 
						||
| 
								 | 
							
								of the various constructors have been invoked from the Python side via code such as:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    from pybind11_tests import ConstructorStats
							 | 
						||
| 
								 | 
							
								    cstats = ConstructorStats.get(MyClass)
							 | 
						||
| 
								 | 
							
								    print(cstats.alive())
							 | 
						||
| 
								 | 
							
								    print(cstats.default_constructions)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage
							 | 
						||
| 
								 | 
							
								collector to actually destroy objects that aren't yet referenced.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								For everything except copy and move constructors and destructors, any extra values given to the
							 | 
						||
| 
								 | 
							
								print_...() function is stored in a class-specific values list which you can retrieve and inspect
							 | 
						||
| 
								 | 
							
								from the ConstructorStats instance `.values()` method.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								In some cases, when you need to track instances of a C++ class not registered with pybind11, you
							 | 
						||
| 
								 | 
							
								need to add a function returning the ConstructorStats for the C++ class; this can be done with:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>,
							 | 
						||
| 
								 | 
							
								py::return_value_policy::reference)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Finally, you can suppress the output messages, but keep the constructor tracking (for
							 | 
						||
| 
								 | 
							
								inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g.
							 | 
						||
| 
								 | 
							
								`track_copy_created(this)`).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								*/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#include "pybind11_tests.h"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#include <list>
							 | 
						||
| 
								 | 
							
								#include <sstream>
							 | 
						||
| 
								 | 
							
								#include <typeindex>
							 | 
						||
| 
								 | 
							
								#include <unordered_map>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class ConstructorStats {
							 | 
						||
| 
								 | 
							
								protected:
							 | 
						||
| 
								 | 
							
								    std::unordered_map<void *, int> _instances; // Need a map rather than set because members can
							 | 
						||
| 
								 | 
							
								                                                // shared address with parents
							 | 
						||
| 
								 | 
							
								    std::list<std::string> _values;             // Used to track values
							 | 
						||
| 
								 | 
							
								                                                // (e.g. of value constructors)
							 | 
						||
| 
								 | 
							
								public:
							 | 
						||
| 
								 | 
							
								    int default_constructions = 0;
							 | 
						||
| 
								 | 
							
								    int copy_constructions = 0;
							 | 
						||
| 
								 | 
							
								    int move_constructions = 0;
							 | 
						||
| 
								 | 
							
								    int copy_assignments = 0;
							 | 
						||
| 
								 | 
							
								    int move_assignments = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    void copy_created(void *inst) {
							 | 
						||
| 
								 | 
							
								        created(inst);
							 | 
						||
| 
								 | 
							
								        copy_constructions++;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    void move_created(void *inst) {
							 | 
						||
| 
								 | 
							
								        created(inst);
							 | 
						||
| 
								 | 
							
								        move_constructions++;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    void default_created(void *inst) {
							 | 
						||
| 
								 | 
							
								        created(inst);
							 | 
						||
| 
								 | 
							
								        default_constructions++;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    void created(void *inst) { ++_instances[inst]; }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    void destroyed(void *inst) {
							 | 
						||
| 
								 | 
							
								        if (--_instances[inst] < 0) {
							 | 
						||
| 
								 | 
							
								            throw std::runtime_error("cstats.destroyed() called with unknown "
							 | 
						||
| 
								 | 
							
								                                     "instance; potential double-destruction "
							 | 
						||
| 
								 | 
							
								                                     "or a missing cstats.created()");
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    static void gc() {
							 | 
						||
| 
								 | 
							
								        // Force garbage collection to ensure any pending destructors are invoked:
							 | 
						||
| 
								 | 
							
								#if defined(PYPY_VERSION)
							 | 
						||
| 
								 | 
							
								        PyObject *globals = PyEval_GetGlobals();
							 | 
						||
| 
								 | 
							
								        PyObject *result = PyRun_String("import gc\n"
							 | 
						||
| 
								 | 
							
								                                        "for i in range(2):\n"
							 | 
						||
| 
								 | 
							
								                                        "    gc.collect()\n",
							 | 
						||
| 
								 | 
							
								                                        Py_file_input,
							 | 
						||
| 
								 | 
							
								                                        globals,
							 | 
						||
| 
								 | 
							
								                                        globals);
							 | 
						||
| 
								 | 
							
								        if (result == nullptr)
							 | 
						||
| 
								 | 
							
								            throw py::error_already_set();
							 | 
						||
| 
								 | 
							
								        Py_DECREF(result);
							 | 
						||
| 
								 | 
							
								#else
							 | 
						||
| 
								 | 
							
								        py::module_::import("gc").attr("collect")();
							 | 
						||
| 
								 | 
							
								#endif
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    int alive() {
							 | 
						||
| 
								 | 
							
								        gc();
							 | 
						||
| 
								 | 
							
								        int total = 0;
							 | 
						||
| 
								 | 
							
								        for (const auto &p : _instances) {
							 | 
						||
| 
								 | 
							
								            if (p.second > 0) {
							 | 
						||
| 
								 | 
							
								                total += p.second;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return total;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    void value() {} // Recursion terminator
							 | 
						||
| 
								 | 
							
								    // Takes one or more values, converts them to strings, then stores them.
							 | 
						||
| 
								 | 
							
								    template <typename T, typename... Tmore>
							 | 
						||
| 
								 | 
							
								    void value(const T &v, Tmore &&...args) {
							 | 
						||
| 
								 | 
							
								        std::ostringstream oss;
							 | 
						||
| 
								 | 
							
								        oss << v;
							 | 
						||
| 
								 | 
							
								        _values.push_back(oss.str());
							 | 
						||
| 
								 | 
							
								        value(std::forward<Tmore>(args)...);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Move out stored values
							 | 
						||
| 
								 | 
							
								    py::list values() {
							 | 
						||
| 
								 | 
							
								        py::list l;
							 | 
						||
| 
								 | 
							
								        for (const auto &v : _values) {
							 | 
						||
| 
								 | 
							
								            l.append(py::cast(v));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        _values.clear();
							 | 
						||
| 
								 | 
							
								        return l;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Gets constructor stats from a C++ type index
							 | 
						||
| 
								 | 
							
								    static ConstructorStats &get(std::type_index type) {
							 | 
						||
| 
								 | 
							
								        static std::unordered_map<std::type_index, ConstructorStats> all_cstats;
							 | 
						||
| 
								 | 
							
								        return all_cstats[type];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Gets constructor stats from a C++ type
							 | 
						||
| 
								 | 
							
								    template <typename T>
							 | 
						||
| 
								 | 
							
								    static ConstructorStats &get() {
							 | 
						||
| 
								 | 
							
								#if defined(PYPY_VERSION)
							 | 
						||
| 
								 | 
							
								        gc();
							 | 
						||
| 
								 | 
							
								#endif
							 | 
						||
| 
								 | 
							
								        return get(typeid(T));
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Gets constructor stats from a Python class
							 | 
						||
| 
								 | 
							
								    static ConstructorStats &get(py::object class_) {
							 | 
						||
| 
								 | 
							
								        auto &internals = py::detail::get_internals();
							 | 
						||
| 
								 | 
							
								        const std::type_index *t1 = nullptr, *t2 = nullptr;
							 | 
						||
| 
								 | 
							
								        try {
							 | 
						||
| 
								 | 
							
								            auto *type_info
							 | 
						||
| 
								 | 
							
								                = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0);
							 | 
						||
| 
								 | 
							
								            for (auto &p : internals.registered_types_cpp) {
							 | 
						||
| 
								 | 
							
								                if (p.second == type_info) {
							 | 
						||
| 
								 | 
							
								                    if (t1) {
							 | 
						||
| 
								 | 
							
								                        t2 = &p.first;
							 | 
						||
| 
								 | 
							
								                        break;
							 | 
						||
| 
								 | 
							
								                    }
							 | 
						||
| 
								 | 
							
								                    t1 = &p.first;
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        } catch (const std::out_of_range &) { // NOLINT(bugprone-empty-catch)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (!t1) {
							 | 
						||
| 
								 | 
							
								            throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        auto &cs1 = get(*t1);
							 | 
						||
| 
								 | 
							
								        // If we have both a t1 and t2 match, one is probably the trampoline class; return
							 | 
						||
| 
								 | 
							
								        // whichever has more constructions (typically one or the other will be 0)
							 | 
						||
| 
								 | 
							
								        if (t2) {
							 | 
						||
| 
								 | 
							
								            auto &cs2 = get(*t2);
							 | 
						||
| 
								 | 
							
								            int cs1_total = cs1.default_constructions + cs1.copy_constructions
							 | 
						||
| 
								 | 
							
								                            + cs1.move_constructions + (int) cs1._values.size();
							 | 
						||
| 
								 | 
							
								            int cs2_total = cs2.default_constructions + cs2.copy_constructions
							 | 
						||
| 
								 | 
							
								                            + cs2.move_constructions + (int) cs2._values.size();
							 | 
						||
| 
								 | 
							
								            if (cs2_total > cs1_total) {
							 | 
						||
| 
								 | 
							
								                return cs2;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return cs1;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// To track construction/destruction, you need to call these methods from the various
							 | 
						||
| 
								 | 
							
								// constructors/operators.  The ones that take extra values record the given values in the
							 | 
						||
| 
								 | 
							
								// constructor stats values for later inspection.
							 | 
						||
| 
								 | 
							
								template <class T>
							 | 
						||
| 
								 | 
							
								void track_copy_created(T *inst) {
							 | 
						||
| 
								 | 
							
								    ConstructorStats::get<T>().copy_created(inst);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T>
							 | 
						||
| 
								 | 
							
								void track_move_created(T *inst) {
							 | 
						||
| 
								 | 
							
								    ConstructorStats::get<T>().move_created(inst);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void track_copy_assigned(T *, Values &&...values) {
							 | 
						||
| 
								 | 
							
								    auto &cst = ConstructorStats::get<T>();
							 | 
						||
| 
								 | 
							
								    cst.copy_assignments++;
							 | 
						||
| 
								 | 
							
								    cst.value(std::forward<Values>(values)...);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void track_move_assigned(T *, Values &&...values) {
							 | 
						||
| 
								 | 
							
								    auto &cst = ConstructorStats::get<T>();
							 | 
						||
| 
								 | 
							
								    cst.move_assignments++;
							 | 
						||
| 
								 | 
							
								    cst.value(std::forward<Values>(values)...);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void track_default_created(T *inst, Values &&...values) {
							 | 
						||
| 
								 | 
							
								    auto &cst = ConstructorStats::get<T>();
							 | 
						||
| 
								 | 
							
								    cst.default_created(inst);
							 | 
						||
| 
								 | 
							
								    cst.value(std::forward<Values>(values)...);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void track_created(T *inst, Values &&...values) {
							 | 
						||
| 
								 | 
							
								    auto &cst = ConstructorStats::get<T>();
							 | 
						||
| 
								 | 
							
								    cst.created(inst);
							 | 
						||
| 
								 | 
							
								    cst.value(std::forward<Values>(values)...);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void track_destroyed(T *inst) {
							 | 
						||
| 
								 | 
							
								    ConstructorStats::get<T>().destroyed(inst);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void track_values(T *, Values &&...values) {
							 | 
						||
| 
								 | 
							
								    ConstructorStats::get<T>().value(std::forward<Values>(values)...);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/// Don't cast pointers to Python, print them as strings
							 | 
						||
| 
								 | 
							
								inline const char *format_ptrs(const char *p) { return p; }
							 | 
						||
| 
								 | 
							
								template <typename T>
							 | 
						||
| 
								 | 
							
								py::str format_ptrs(T *p) {
							 | 
						||
| 
								 | 
							
								    return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p));
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <typename T>
							 | 
						||
| 
								 | 
							
								auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) {
							 | 
						||
| 
								 | 
							
								    return std::forward<T>(x);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								template <class T, typename... Output>
							 | 
						||
| 
								 | 
							
								void print_constr_details(T *inst, const std::string &action, Output &&...output) {
							 | 
						||
| 
								 | 
							
								    py::print("###",
							 | 
						||
| 
								 | 
							
								              py::type_id<T>(),
							 | 
						||
| 
								 | 
							
								              "@",
							 | 
						||
| 
								 | 
							
								              format_ptrs(inst),
							 | 
						||
| 
								 | 
							
								              action,
							 | 
						||
| 
								 | 
							
								              format_ptrs(std::forward<Output>(output))...);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Verbose versions of the above:
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void print_copy_created(T *inst,
							 | 
						||
| 
								 | 
							
								                        Values &&...values) { // NB: this prints, but doesn't store, given values
							 | 
						||
| 
								 | 
							
								    print_constr_details(inst, "created via copy constructor", values...);
							 | 
						||
| 
								 | 
							
								    track_copy_created(inst);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void print_move_created(T *inst,
							 | 
						||
| 
								 | 
							
								                        Values &&...values) { // NB: this prints, but doesn't store, given values
							 | 
						||
| 
								 | 
							
								    print_constr_details(inst, "created via move constructor", values...);
							 | 
						||
| 
								 | 
							
								    track_move_created(inst);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void print_copy_assigned(T *inst, Values &&...values) {
							 | 
						||
| 
								 | 
							
								    print_constr_details(inst, "assigned via copy assignment", values...);
							 | 
						||
| 
								 | 
							
								    track_copy_assigned(inst, values...);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void print_move_assigned(T *inst, Values &&...values) {
							 | 
						||
| 
								 | 
							
								    print_constr_details(inst, "assigned via move assignment", values...);
							 | 
						||
| 
								 | 
							
								    track_move_assigned(inst, values...);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void print_default_created(T *inst, Values &&...values) {
							 | 
						||
| 
								 | 
							
								    print_constr_details(inst, "created via default constructor", values...);
							 | 
						||
| 
								 | 
							
								    track_default_created(inst, values...);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void print_created(T *inst, Values &&...values) {
							 | 
						||
| 
								 | 
							
								    print_constr_details(inst, "created", values...);
							 | 
						||
| 
								 | 
							
								    track_created(inst, values...);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
							 | 
						||
| 
								 | 
							
								    print_constr_details(inst, "destroyed", values...);
							 | 
						||
| 
								 | 
							
								    track_destroyed(inst);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								template <class T, typename... Values>
							 | 
						||
| 
								 | 
							
								void print_values(T *inst, Values &&...values) {
							 | 
						||
| 
								 | 
							
								    print_constr_details(inst, ":", values...);
							 | 
						||
| 
								 | 
							
								    track_values(inst, values...);
							 | 
						||
| 
								 | 
							
								}
							 |