File: objectclass.hpp

package info (click to toggle)
cppgir 2.0%2Bgit20250629.2a7d9ce-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,220 kB
  • sloc: cpp: 16,451; ansic: 355; python: 86; makefile: 13; sh: 9
file content (1562 lines) | stat: -rw-r--r-- 49,662 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
#ifndef GI_OBJECTCLASS_HPP
#define GI_OBJECTCLASS_HPP

/**
 * The purposes of the code and classes here is to perform registration of
 * a new GType type for a custom class that (suitably) inherits from these.
 * Setting up a new GType involves registration of a new type (and its signals
 * and properties).  This involves potentially custom overridden implementations
 * of the parent class(es) (C-style virtual) methods (= entries in class
 * structure) and likewise so for the interfaces.  For either of the latter
 * cases, pointers to wrapper functions are inserted in the class/interface
 * struct, that will in turn call generated C++ virtual methods
 * (which have presumably been overridden by the custom subclass).
 *
 * The tricky part in the above is that GType/C methods are "optional",
 * in that an entry in a class/interface struct can be left as NULL,
 * and it is a common pattern for calling code to check for such
 * (rather than unconditionally calling).  To preserve such behaviour,
 * an entry in a class/interface struct should only be "routed" to a
 * C++ virtual method if the custom SubClass actually has a definition for it.
 * The approach below tries to detect this (basically by checking if
 * SubClass::method is a different type than Base::method).  But that might
 * fail (with compilation errors due to overload resolution failure), so there
 * is also a system to manually specify what is defined, e.g. whether the
 * corresponding entry in the class/interface struct should be filled
 * for the SubClass' generated GType.
 *
 */

#include "callback.hpp"
#include "object.hpp"

GI_MODULE_EXPORT
namespace gi
{
// slightly nasty; will be generated
namespace repository
{
namespace GObject
{
enum class SignalFlags : std::underlying_type<::GSignalFlags>::type;
}
} // namespace repository

// specify construction type when creating ObjectClass based object
struct construct_t
{
  const int value;
  constexpr explicit construct_t(int v = 0) : value(v) {}
};
struct construct_auto_t : public construct_t
{
  constexpr construct_auto_t() : construct_t(0) {}
};
struct construct_cpp_t : public construct_t
{
  constexpr construct_cpp_t() : construct_t(1) {}
};
struct construct_c_t : public construct_t
{
  constexpr construct_c_t() : construct_t(2) {}
};

namespace detail
{
class ObjectBaseClass : public noncopyable
{
public:
  virtual ~ObjectBaseClass() {}

  ObjectBaseClass() : gobject_(nullptr) {}

  ObjectBaseClass(ObjectBaseClass &&other)
  {
    gobject_ = other.gobject_;
    other.gobject_ = nullptr;
  }

  ObjectBaseClass &operator=(ObjectBaseClass &&other)
  {
    if (this != &other) {
      gobject_ = other.gobject_;
      other.gobject_ = nullptr;
    }
    return *this;
  }

protected:
  typedef void (*interface_register_function)(GType class_type, gpointer init);
  void request_interface(interface_register_function reg, gpointer init)
  {
    itfs.emplace_back(reg, init);
  }

  GType base_gtype()
  {
    return gobject_ ? g_type_parent(G_OBJECT_TYPE(gobject_)) : G_TYPE_NONE;
  }

protected:
  // owns 1 ref (possibly managed externally)
  GObject *gobject_;
  // GType in case no gobject_
  GType gtype_;
  // additional type setup
  using interface_init_t = std::pair<interface_register_function, gpointer>;
  using interface_inits_t = std::vector<interface_init_t>;
  interface_inits_t itfs;
};

//// interface setup ////

// virtual inheritance as multiple inheritance will be used
// however, only 1 base instance should be around
class InterfaceClass : public virtual ObjectBaseClass
{
protected:
  static void add_interface(GType class_type, GType itf_type,
      GInterfaceInitFunc itf_init, gpointer init_data)
  {
    GInterfaceInfo itf_info = {
        itf_init, // interface_init
        nullptr,  // interface_finalize
        init_data // interface_data
    };

    g_type_add_interface_static(class_type, itf_type, &itf_info);
  }
};

template<typename ClassDef, typename SubClass>
gpointer forward_make_type_init_data();

template<typename InterfaceDef>
class InterfaceImpl : public InterfaceDef, public InterfaceClass
{
  typedef typename InterfaceDef::instance_type instance_type_t;

protected:
  static void register_interface(GType class_type, gpointer init_data)
  {
    add_interface(class_type, InterfaceDef::instance_type::get_type_(),
        InterfaceDef::interface_init, init_data);
  }

  // init data for registration-only use
  template<typename SubClass>
  static ObjectBaseClass::interface_init_t interface_init_data()
  {
    return {InterfaceImpl::register_interface,
        (gpointer)forward_make_type_init_data<InterfaceImpl, SubClass>()};
  }

  InterfaceImpl(gpointer init_data = nullptr)
  {
    request_interface(InterfaceImpl::register_interface, init_data);
  }

  // conversion to regular interface side
  instance_type_t interface_(gi::interface_tag<instance_type_t>)
  {
    return gi::wrap(
        (typename instance_type_t::BaseObjectType *)g_object_ref(gobject_),
        gi::transfer_full);
  }

  instance_type_t interface_()
  {
    return gi::wrap(
        (typename instance_type_t::BaseObjectType *)g_object_ref(gobject_),
        gi::transfer_full);
  }
};

template<typename InterfaceImpl>
class InterfaceClassImpl : public InterfaceImpl
{
  typedef typename InterfaceImpl::instance_type::BaseObjectType *c_type;
  typedef typename InterfaceImpl::interface_type interface_type_t;

  // use runtime data to avoid code generation of template
  // interface_type_t *istruct_;
  GType klass_;

protected:
  static interface_type_t *get_struct_(GType ktype)
  {
    auto klass = g_type_class_peek(ktype);
    return (interface_type_t *)g_type_interface_peek(
        klass, InterfaceImpl::instance_type::get_type_());
  }

  interface_type_t *get_struct_() { return get_struct_(klass_); }

  c_type gobj_() { return (c_type)this->gobject_; }

  InterfaceClassImpl(GType klass, gpointer itf_init_data = nullptr)
      : InterfaceImpl(itf_init_data), klass_(klass)
  {}
};

class ObjectClass;
class PropertyBase;

inline GQuark
object_data_quark()
{
  static const char *OBJECT_DATA_KEY = "GIOBJECT_OBJECT";
  static GQuark q = g_quark_from_static_string(OBJECT_DATA_KEY);
  return q;
}

using repository::GObject::Object;
using repository::GObject::ParamSpec;
using repository::GObject::SignalFlags;

//// init data collection ////

// a function pointer that serves as factory for the actual class_init data
// (e.g. holds data on which methods have been overridden)
// (void return to avoid function cast warning; returns real data otherwise)
typedef void (*type_init_data_factory_t)();
template<typename ClassDef, typename SubClass,
    typename std::enable_if<std::is_same<SubClass, void>::value>::type * =
        nullptr>
type_init_data_factory_t
make_type_init_data()
{
  return nullptr;
}

template<typename BaseDef, typename SubClass,
    typename std::enable_if<!std::is_same<SubClass, void>::value>::type * =
        nullptr>
type_init_data_factory_t
make_type_init_data()
{
  // hard cast; should return meaningful data
  return (type_init_data_factory_t)&BaseDef::TypeInitData::template factory<
      SubClass>;
}

template<typename ClassDef, typename SubClass>
gpointer
forward_make_type_init_data()
{
  return (gpointer)make_type_init_data<ClassDef, SubClass>();
}

// in generated code;
// each class/interface member (function) is assocated with a single type
// base class for tagged boolean member types
// (default to true for legacy case where no init data is captured from type)
template<typename Tag>
struct member_type
{
  bool value;
  constexpr member_type(bool v = true) : value(v) {}
  explicit operator bool() { return value; }
};

// combine manual Spec with code generated Default
template<typename Spec, typename Default>
struct Combine : public Spec, public Default
{
  using Default::has_definition;
  using Spec::defines;
  // dispatch from query signature to default if no manual specificiation
  template<typename MemberType, typename SubClass>
  constexpr static bool defines(const MemberType *m, const SubClass *cl)
  {
    return has_definition(m, cl);
  };
};

template<typename SubClass, typename Default>
using DefinitionData = Combine<typename SubClass::DefinitionData, Default>;

// see objectclass macros in gi_inc for how the above is used

//// class setup ////

// this collects properties that will be installed on a class type
// during the class_init (as opposed to added later on by property members)
// this is especially needed to install properties required by an interface
// (as those are checked by object_interface_check_properties early on)
typedef std::map<std::string, std::pair<PropertyBase *, ParamSpec>> properties;

template<typename T>
class property;

template<typename T, typename Base = repository::GObject::Object>
class signal;

struct ParamSpecInit : public ParamSpec
{
  cstring_v name_;
  ParamSpec spec_;

  // full spec arguments
  template<typename T, typename P, typename... Args,
      typename std::enable_if<sizeof...(Args) != 1>::type * = nullptr>
  ParamSpecInit(property<P> T::*, Args &&...args)
      : spec_(ParamSpec::new_<P>(std::forward<Args>(args)...))
  {}

  // name suffices for override property
  template<typename T, typename P>
  ParamSpecInit(property<P> T::*, const cstring_v name) : name_(name)
  {}
};

struct properties_init : public properties
{
  properties_init(std::initializer_list<ParamSpecInit> pd)
  {
    for (auto &&e : pd) {
      // should have either one
      auto name = e.name_;
      if (e.spec_)
        name = e.spec_.name_();
      insert({name, {nullptr, e.spec_}});
    }
  }
};

struct SignalSpec
{
  GType ret_type;
  std::vector<GType> types;
  gi::cstring_v name;
  SignalFlags flags;

  template<typename T, typename R, typename Instance, typename... Args>
  SignalSpec(signal<R(Instance, Args...)> T::*, const gi::cstring_v _name,
      SignalFlags _flags = (SignalFlags)0)
      : ret_type(gi::traits::gtype<R>::get_type()),
        types({gi::traits::gtype<Args>::get_type()...}), name(_name),
        flags(_flags)
  {}
};

using SignalSpecInit = SignalSpec;

using signals = std::vector<SignalSpec>;
using signals_init = signals;

inline void class_init_props_sigs(
    ObjectClass *impl, gpointer g_class, gpointer props, gpointer sigs);

inline gi::cstring_v
klass_type_name(const std::type_info &ti, gi::cstring_v klassname)
{
  return (klassname && klassname.at(0)) ? klassname : ti.name();
}

template<typename T, typename Enable = void>
struct custom_class_init : public std::false_type
{
  static constexpr GClassInitFunc value = nullptr;
};

template<typename T>
struct custom_class_init<T,
    typename traits::if_valid_type<decltype(T::custom_class_init)>::type>
    : public std::true_type
{
  static constexpr auto value = T::custom_class_init;
};

template<typename T, typename Enable = void>
struct custom_instance_init : public std::false_type
{
  static constexpr GInstanceInitFunc value = nullptr;
};

template<typename T>
struct custom_instance_init<T,
    typename traits::if_valid_type<decltype(T::custom_init)>::type>
    : public std::true_type
{
  static constexpr auto value = T::custom_init;
};

class ObjectClass : public virtual ObjectBaseClass
{
public:
  typedef gi::repository::GObject::Object instance_type;
  typedef GObjectClass class_type;
  // above init code refers to this inner type unconditionally
  // so arrange for a fallback in baseclass
  struct DefinitionData
  {
    constexpr static bool defines(...) { return false; }
  };

  // used as single argument in constructor for c-first style subclass
  struct InitData
  {
    mutable Object instance;

    explicit operator bool() const { return instance.gobj_(); }

  protected:
    // only for internal use
    // and also complicates calling any function/constructor using this type
    // silly dummy argument prevents ambiguity in ObjectImpl constructors
    // when specifying all {}
    explicit InitData(int) {}
    friend class ObjectClass;
  };

protected:
  struct ClassInitNode
  {
    GClassInitFunc self;
    type_init_data_factory_t class_init_data_factory;
    const ClassInitNode *child;
  };

private:
  static const constexpr char *CLASS_PREFIX = "GIOBJECT__";

  static std::string canonical_name(const std::string &name)
  {
    auto result = name;
    for (auto &p : result) {
      if (!(g_ascii_isalnum(p) || p == '_' || p == '-'))
        p = '+';
    }
    return result;
  }

  typedef void (*GObjectConstructed)(GObject *object);
  typedef GObject *(*GObjectConstructor)(GType type,
      guint n_construct_properties,
      GObjectConstructParam *construct_properties);

  typedef std::vector<std::pair<GClassInitFunc, type_init_data_factory_t>>
      class_inits_t;
  struct class_data_t
  {
    std::unique_ptr<class_inits_t> class_inits;
    GClassInitFunc custom_class_init;
    const properties *props;
    const signals *sigs;
    ObjectClass *impl;
    GObjectConstructor constructor;
  };

  struct custom_inits_t
  {
    GClassInitFunc custom_class_init{};
    GInstanceInitFunc custom_init{};
    GObjectConstructor constructor{};
    GObjectConstructed constructed{};
  };

  static void class_init_all(gpointer g_class, gpointer class_data)
  {
    std::unique_ptr<class_data_t> data((class_data_t *)class_data);

    // class_init below is called with top-level class_data,
    // others (= code generated) with their own
    for (auto &&ci : *data->class_inits)
      ci.first(
          g_class, ci.first == &class_init ? class_data : gpointer(ci.second));
  }

  static void class_init(gpointer g_class, gpointer class_data)
  {
    class_data_t *data = ((class_data_t *)class_data);
    // delegate property handling
    class_init_props_sigs(
        data->impl, g_class, (gpointer)data->props, (gpointer)data->sigs);
    // set constructed to invoke constructor
    if (data->constructor)
      ((GObjectClass *)(g_class))->constructor = data->constructor;
    // also call top-level class custom class init
    if (data->custom_class_init) {
      data->custom_class_init(g_class, nullptr);
    }
  }

  GType register_type(GType base_type, const gi::cstring_v klassname,
      const ClassInitNode &init_node, const interface_inits_t &itfs,
      const properties &props, const signals &sigs, custom_inits_t custom_inits)
  {
    // nothing to do if already registered
    GType custom_type = g_type_from_name(klassname.c_str());
    if (custom_type)
      return custom_type;

    // otherwise create with same class/instance size as parent type
    GTypeQuery base_query = {
        0,
        nullptr,
        0,
        0,
    };
    g_type_query(base_type, &base_query);

    const guint16 class_size = (guint16)base_query.class_size;
    const guint16 instance_size = (guint16)base_query.instance_size;

    // collect chain of class inits
    std::unique_ptr<class_inits_t> class_inits(new class_inits_t());
    auto node = &init_node;
    while (node) {
      if (node->self)
        class_inits->push_back({node->self, node->class_init_data_factory});
      node = node->child;
    }

    // assemble class_data
    std::unique_ptr<class_data_t> class_data(new class_data_t());
    // class creation will be triggered upon instance creation
    // which will happen shortly after this class
    // (so the list has to handled special, but the others will still be
    // around)
    class_data->class_inits = std::move(class_inits);
    class_data->custom_class_init = custom_inits.custom_class_init;
    class_data->impl = this;
    class_data->props = &props;
    class_data->sigs = &sigs;
    class_data->constructor = custom_inits.constructor;

    const GTypeInfo derived_info = {
        class_size,
        nullptr, // base_init
        nullptr, // base_finalize
        class_init_all,
        nullptr,              // class_finalize
        class_data.release(), // class_data
        instance_size,
        0,                        // n_preallocs
        custom_inits.custom_init, // instance_init
        nullptr,                  // value_table
    };

    custom_type = g_type_register_static(
        base_type, klassname.c_str(), &derived_info, GTypeFlags(0));

    // handle interfaces
    for (auto &&itf : itfs)
      itf.first(custom_type, itf.second);

    // force/finish class creation,
    // so a subsequent _peek does not return NULL
    // (and lists above are still around)
    g_type_class_unref(g_type_class_ref(custom_type));

    return custom_type;
  }

  // minor convenience wrap for the above
  GType register_type(GType parent, const gi::cstring_v klassname,
      const ClassInitNode *node, const properties &props, const signals &sigs,
      custom_inits_t custom_inits)
  {
    auto gtype = register_type(parent, klassname, {class_init, nullptr, node},
        itfs, props, sigs, custom_inits);
    itfs.clear();
    return gtype;
  }

  void setup_instance(GType gtype,
      const repository::GObject::construct_params &params,
      gpointer instance = nullptr)
  {
    // not good if provided instance already has associated C++ instance
    g_return_if_fail(!instance || !ObjectClass::instance((GObject *)instance));
    g_return_if_fail(gtype);

    // no longer needed`
    itfs.clear();
    // create and link object instance
    // if needed, that is, otherwise use provided instance and tie onto that one
    gtype_ = gtype;
    GObject *obj = gobject_ =
        (GObject *)(instance ? instance : Object::new_(gtype, params));
    // should still be floating, then we assume ownership
    // if it is no longer, then it has already been stolen (e.g. GtkWindow),
    // and we need to add one here
    if (!instance && g_type_is_a(gtype, G_TYPE_INITIALLY_UNOWNED))
      g_object_ref_sink(gobject_);
    // mark this as associated wrapper object as retrieved by .instance()
    g_object_set_qdata_full(obj, object_data_quark(), this, destroy_notify);
  }

  // C++ side construction
  // always (try to) register type and create instance
  template<typename SubClass>
  void register_setup(GType parent, const gi::cstring_v klassname,
      const ClassInitNode *node,
      const repository::GObject::construct_params &params,
      const properties &props, const signals &sigs, custom_inits_t custom_inits,
      gpointer instance = nullptr, std::nullptr_t = nullptr)
  {
    // sort-of internal prefixed klassname
    auto custom_name = std::string(CLASS_PREFIX) + canonical_name(klassname);
    auto gtype =
        register_type(parent, custom_name, node, props, sigs, custom_inits);
    setup_instance(gtype, params, instance);
  }

  template<typename SubClass>
  static GObject *instance_constructor(GType type, guint n_construct_properties,
      GObjectConstructParam *construct_properties)
  {
    // chain up, but we can skip other parent variations of this function
    auto gtype = SubClass::baseclass_type::get_type_();
    auto klass = G_OBJECT_CLASS(g_type_class_peek(gtype));
    auto instance =
        klass->constructor(type, n_construct_properties, construct_properties);

    g_assert(instance);
    // there should be no C++ side yet
    // as we chain up past any possible C++ parent
    g_assert(!ObjectClass::instance(instance));

    // handle C++ setup
    auto floating = g_object_is_floating(instance);
    // avoid inadvertent sink
    InitData id{0};
    id.instance = gi::wrap((GObject *)g_object_ref(instance), transfer_full);
#if GI_CONFIG_EXCEPTIONS
    try {
#endif
      auto self = new SubClass(id);
      // sanity check on ref
      if (floating && !g_object_is_floating(instance)) {
        g_warning("%s constructor sinks instance", typeid(SubClass).name());
        // try to unsink if it seems safe and applicable
        auto obj = (GObject *)instance;
        // theoretically not MT safe, but if == 1, only 1 thread should be
        // involved
        if (obj->ref_count == 1) {
          g_warning("re-floating instance");
          g_object_force_floating(obj);
        }
      }
      (void)self;
      // self->setup is essentially run below at bottom of constructor chain)
      // this will then assign ownership of self to instance
      g_assert(self->gobj_() == (gpointer)instance);
#if GI_CONFIG_EXCEPTIONS
    } catch (const std::exception &exc) {
      // bad things will happen
      report_exception(exc);
      g_critical("constructor failed in instance_init");
    }
#endif

    return instance;
  }

  // C-side construction
  // either register type, or finish setup of an instance (triggered on C side_
  template<typename SubClass>
  void register_setup(GType parent, const gi::cstring_v klassname,
      const ClassInitNode *node,
      const repository::GObject::construct_params &params,
      const properties &props, const signals &sigs, custom_inits_t custom_inits,
      gpointer instance = nullptr, const InitData *id = {})
  {
    g_assert(id);
    g_assert(!instance);
    // no real instance, so used for type registration purpose
    if (!id->instance) {
      // these parts are not applicable
      (void)instance;
      (void)params;
      g_assert(!instance);
      g_assert(params.empty());
      // we have our own custom init, which creates the cpp object
      // so the latter's constructor serves as custom init
      // custom_inits.custom_init = instance_init<SubClass>;
      custom_inits.constructor = instance_constructor<SubClass>;
      // there is no gobject_ instance
      // make sure to track type for subsequent signal and property registration
      gtype_ =
          register_type(parent, klassname, node, props, sigs, custom_inits);
      g_assert(gtype_);
    } else {
      // so this is a new instance as created by instance_init
      // ensure instance association here at the end of constructor chain
      // so it is that way for the sequel of the subclass constructor
      // (and redundant signal and property registration)
      instance = id->instance.gobj_();
      setup_instance(G_OBJECT_TYPE(instance), {}, instance);
    }
  }

  static void destroy_notify(gpointer data)
  {
    ObjectClass *impl = (ObjectClass *)data;
    // sever link with object instance
    impl->gobject_ = nullptr;
    delete impl;
  }

protected:
  template<typename SubClass>
  static custom_inits_t make_custom_inits()
  {
    return {GClassInitFunc(custom_class_init<SubClass>::value),
        GInstanceInitFunc(custom_instance_init<SubClass>::value)};
  }

  ObjectClass(GType parent, const gi::cstring_v klassname,
      const ClassInitNode &node,
      const repository::GObject::construct_params &params,
      const properties &props)
  {
    register_setup<void>(
        parent, klassname, &node, params, props, {}, {}, nullptr, nullptr);
  }

  template<typename SubClass, typename InitData>
  ObjectClass(const SubClass *, GType parent, const gi::cstring_v klassname,
      const ClassInitNode &node,
      const repository::GObject::construct_params &params,
      const properties &props, gpointer instance, InitData id)
  {
    register_setup<SubClass>(parent, klassname, &node, params, props, {},
        make_custom_inits<SubClass>(), instance, id);
  }

  template<typename SubClass>
  ObjectClass(const SubClass *, const gi::cstring_v klassname, GType base,
      const ObjectClass::ClassInitNode &node, const interface_inits_t &itfs,
      const properties &props, const signals &sigs)
  {
    this->itfs = itfs;
    auto id = InitData{0};
    register_setup<SubClass>(base, klassname, &node, {}, props, sigs,
        make_custom_inits<SubClass>(), nullptr, &id);
  }

  ~ObjectClass()
  {
    // object destruction should typically be initiated from the
    // associated object instance based on refcount,
    // and so pass through destroy_notify, in which case no more gobject_
    // but it could come here first for a stack based custom object or alike
    if (gobject_) {
      // corresponding object should not have outstanding refs out there
      // not good otherwise, and why it should not be destructed this way
      if (gobject_->ref_count != 1)
        g_error("destroying object with outstanding object refs");
      // NOTE the unref might still trigger vmethod calls,
      // but destruction already happened down to this level,
      // so derived cast and vmethod call no longer possible
      // so sever link anyway to make that clear
      g_object_steal_qdata(gobject_, object_data_quark());
      g_object_unref(gobject_);
    }
  }

public:
  ObjectClass(const std::type_info &ti,
      const repository::GObject::construct_params &params = {},
      const properties &props = {})
  {
    register_setup<void>(instance_type::get_type_(), ti.name(), nullptr, params,
        props, {}, {}, nullptr, nullptr);
  }

  template<typename SubClass, typename InitData = std::nullptr_t>
  ObjectClass(const SubClass *,
      const repository::GObject::construct_params &params = {},
      const properties &props = {}, gpointer instance = nullptr,
      const gi::cstring_v klassname = nullptr, InitData id = nullptr)
  {
    const auto &ti = typeid(SubClass);
    register_setup<SubClass>(instance_type::get_type_(),
        klass_type_name(ti, klassname), nullptr, params, props, {},
        make_custom_inits<SubClass>(), instance, id);
  }

  operator Object() { return gi::wrap(gobject_, transfer_none); }

  GType gobj_klass_type()
  {
    // there should almost always be an object instance
    // except during initial registration of c-first style
    return gobject_ ? G_OBJECT_TYPE(gobject_) : gtype_;
  }

  GObjectClass *gobj_klass()
  {
    return (GObjectClass *)g_type_class_peek(gobj_klass_type());
  }

  static ObjectClass *instance(GObject *gobject)
  {
    return (ObjectClass *)g_object_get_qdata(gobject, object_data_quark());
  }

private:
  template<typename T>
  friend GType register_type();

  template<typename T>
  static GType register_type_()
  {
    // check if the class has defined a get_type_()
    // rather than nifty compile-time checks, simply use runtme
    auto btype = T::baseclass_type::get_type_();
    auto gtype = T::get_type_();
    if (gtype == btype) {
      // so nothing happened, use constructor to register
      // instantiate dummy instance that registers type
      gtype = (T{InitData{0}}).gobj_klass_type();
    }
    return gtype;
  }
};

// interfaces need to go left; constructors need to run first (to request
// interface) class constructor deals with class_init last
template<typename ClassDef, typename BaseClass, typename... Interfaces>
class ClassTemplate : public ClassDef, public Interfaces..., public BaseClass
{
  typedef typename ClassDef::instance_type::BaseObjectType *c_type;
  typedef typename ClassDef::class_type class_type_t;
  typedef typename ClassDef::instance_type instance_type_t;

  // make private
  using ClassDef::class_init;

protected:
  class_type_t *get_struct_()
  {
    return (class_type_t *)g_type_class_peek(this->base_gtype());
  }

  c_type gobj_() { return (c_type)this->gobject_; }

  // constructor to be used by custom subclass
  [[deprecated]] ClassTemplate(const std::type_info &ti,
      const repository::GObject::construct_params &params = {},
      const properties &props = {})
      : Interfaces(instance_type_t::get_type_())...,
        BaseClass(instance_type_t::get_type_(), ti.name(),
            {&ClassDef::class_init, nullptr, nullptr}, params, props)
  {}

  // constructor for inner inheritance chain
  [[deprecated]] ClassTemplate(GType base, const gi::cstring_v klassname,
      const ObjectClass::ClassInitNode &node,
      const repository::GObject::construct_params &params,
      const properties &props)
      : Interfaces(instance_type_t::get_type_())...,
        BaseClass(base, klassname, {&ClassDef::class_init, nullptr, &node},
            params, props)
  {}

  // as above, new style
  // constructor to be used by custom subclass
  template<typename SubClass, typename InitData = std::nullptr_t>
  ClassTemplate(const SubClass *sub,
      const repository::GObject::construct_params &params = {},
      const properties &props = {}, gpointer instance = nullptr,
      const gi::cstring_v klassname = nullptr, InitData id = nullptr)
      : Interfaces(instance_type_t::get_type_(),
            gpointer(make_type_init_data<Interfaces, SubClass>()))...,
        BaseClass(sub, instance_type_t::get_type_(),
            klass_type_name(typeid(SubClass), klassname),
            {&ClassDef::class_init, make_type_init_data<ClassDef, SubClass>(),
                nullptr},
            params, props, instance, id)
  {}

  // constructor for inner inheritance chain
  template<typename SubClass, typename InitData = std::nullptr_t>
  ClassTemplate(const SubClass *sub, GType base, const gi::cstring_v klassname,
      const ObjectClass::ClassInitNode &node,
      const repository::GObject::construct_params &params,
      const properties &props, gpointer instance = nullptr,
      InitData id = nullptr)
      : Interfaces(instance_type_t::get_type_(),
            gpointer(make_type_init_data<Interfaces, SubClass>()))...,
        BaseClass(sub, base, klassname,
            {&ClassDef::class_init, make_type_init_data<ClassDef, SubClass>(),
                &node},
            params, props, instance, id)
  {}

  // constructor used in inner inheritance chain for registration collection
  template<typename SubClass>
  ClassTemplate(const SubClass *sub, const gi::cstring_v klassname, GType base,
      const ObjectClass::ClassInitNode &node,
      const typename ClassTemplate::interface_inits_t &itfs,
      const properties &props, const signals &sigs)
      : Interfaces(instance_type_t::get_type_(),
            gpointer(make_type_init_data<Interfaces, SubClass>()))...,
        BaseClass(sub, klassname, base,
            {&ClassDef::class_init, make_type_init_data<ClassDef, SubClass>(),
                &node},
            itfs, props, sigs)
  {}

public:
  class_type_t *gobj_klass() { return (class_type_t *)BaseClass::gobj_klass(); }

  // repeat to disambiguate
  typedef typename ClassDef::instance_type instance_type;

  // access to regular object side
  instance_type_t object_()
  {
    auto obj = this->gobject_ ? g_object_ref(this->gobject_) : nullptr;
    return gi::wrap(
        (typename instance_type_t::BaseObjectType *)(obj), gi::transfer_full);
  }
};

// NOTE as the impl and regular object side are both inherited here,
// ambiguity might not only result from inheriting multiple interfaces,
// but also between either of these sides
// as such, no operator cast is added on the impl side
// instead, use the object_() member to pass to/through regular side
template<typename ObjectT, typename ClassT>
class ObjectImpl : public ObjectT, public ClassT
{
public:
  typedef typename ClassT::instance_type baseclass_type;
  using ObjectT::gobj_;

protected:
  [[deprecated]] ObjectImpl(const std::type_info &ti,
      const repository::GObject::construct_params &params =
          repository::GObject::construct_params{},
      const properties &props = properties{})
      : ClassT(ti, params, props)
  { // link object ptrs (untracked by ObjectBase)
    this->data_ = this->gobject_;
  }

  // NOTE only 1 gtype will be registered,
  // so all (subclass) constructors should specify consistent/same data
  template<typename SubClass>
  ObjectImpl(const SubClass *sub,
      const repository::GObject::construct_params &params =
          repository::GObject::construct_params{},
      const properties &props = properties{})
      : ClassT(sub, params, props)
  { // link object ptrs (untracked by ObjectBase)
    this->data_ = this->gobject_;
  }

  // special advanced case (for internal/override use) by custom subclass
  // where constructed instance is associated with provided object instance
  // (rather than the latter created as part of construction, as usual)
  // if klassname KlassName is specified,
  // registered typename is GIOBJECT__KlassName
  // CAUTION the approach below is more likely applicable
  template<typename SubClass>
  ObjectImpl(ObjectT instance, const SubClass *sub,
      const gi::cstring_v klassname = nullptr,
      const repository::GObject::construct_params &params =
          repository::GObject::construct_params{},
      const properties &props = properties{})
      : ClassT(sub, params, props, instance.gobj_(), klassname)
  { // link object ptrs (untracked by ObjectBase)
    this->data_ = this->gobject_;
  }

  // this will either;
  // + register an object GType with an instance_init that
  //   new()'s a corresponding cpp object (and associates suitably)
  //   so, it can be safely created based on GType (e.g. by some C-factory)
  //   (InitData is essentially empty in this case,
  //    and the cpp ObjectImpl instance is a transient dummy)
  // + construct instance invoked as part of the aforementioned new()
  //   (InitData then holds C object instance)
  // registered type is klassname (as-is)
  template<typename SubClass>
  ObjectImpl(const SubClass *sub, const ObjectClass::InitData &id,
      const gi::cstring_v klassname = {},
      const properties &props = properties{})
      : ClassT(sub, {}, props, nullptr, klassname, &id)
  { // should have a name if this is used to register a type
    g_return_if_fail(id || !klassname.empty());
    // link object ptrs (untracked by ObjectBase)
    this->data_ = this->gobject_;
  }

  ~ObjectImpl()
  { // disconnect (avoid ObjectBase management)
    this->data_ = nullptr;
  }

  // registers a type (instead of using that part of the constructor above)
  // if parent is 0, then it defaults to the type of the immediate parent
  // (so it should only be specified if subclass'ing a subclass)
  // itfs: use {I::interface_init_data(), ...} for *immediate* parents I,
  //    *not* parent interfaces of parents
  // props, signals: use succinct list-initialization (as used elsewhere)
  template<typename SubClass>
  static GType register_type_(const gi::cstring_v klassname, GType parent,
      const typename ClassT::interface_inits_t &itfs,
      const properties_init &props, const signals_init &sigs)
  {
    // collect all data using constructor chain and register type
    // MT-safe according to C++11 specs
    static GType gtype =
        ObjectImpl((SubClass *)nullptr, klassname, parent, itfs, props, sigs)
            .gobj_klass_type();
    return gtype;
  }

private:
  // circumvent protected constructors/destructors
  template<typename SubClass>
  ObjectImpl(const SubClass *sub, const gi::cstring_v klassname, GType parent,
      const typename ClassT::interface_inits_t &itfs,
      const properties_init &props, const signals_init &sigs)
      : ClassT(sub, klassname, parent ? parent : baseclass_type::get_type_(),
            {nullptr, nullptr, nullptr}, itfs, props, sigs)
  {}
};

// wrapper helper to call virtual method
// used in implementation, so we can casually use types in default argument
template<typename C, typename T, typename RetTransfer,
    typename ArgTransfers = void,
    typename CSig = typename map_cpp_function<T,
        typename std::conditional<std::is_null_pointer<RetTransfer>::value, T,
            void>::type>::type>
struct method_wrapper;

template<typename C, typename R, typename... Args, typename RetTransfer,
    typename... Transfers, typename CR, typename... CArgs>
struct method_wrapper<C, R (*)(Args...), RetTransfer, std::tuple<Transfers...>,
    CR(CArgs...)>
{
private:
  typedef R (C::*member_type)(Args...);

  struct caller_data
  {
    C *this_;
    const member_type m;
  };
  static R caller(Args &&...args, void *user_data)
  {
    auto d = (caller_data *)user_data;
    return ((d->this_)->*(d->m))(std::forward<Args>(args)...);
  }

public:
  template<member_type m>
  static CR wrapper(
      typename traits::ctype<typename C::instance_type>::type p, CArgs... args)
  {
    ObjectClass *oc = ObjectClass::instance((GObject *)p);
    C *c = dynamic_cast<C *>(oc);
    if (!oc) {
      // connection already severed by heap destruction
      // use refptr instead of stack allocation if this is a problem
      g_error("missing object");
    } else if (!c) {
      // on our way to crash anyway
      g_error("wrong object type");
    }
    caller_data d{c, m};
    return transform_caller<R(Args...), RetTransfer, std::tuple<Transfers...>,
        CR(CArgs...)>::wrapper(args..., caller, &d);
  }
};

// simplified special case for plain/raw fallback with no wrapping/transfer
// (pick std::nullptr_t for specialization to represent absence of transfer)
template<typename C, typename R, typename... Args>
struct method_wrapper<C, R (*)(Args...), std::nullptr_t>
{
private:
  typedef R (C::*member_type)(Args...);

public:
  template<member_type m>
  static R wrapper(
      typename traits::ctype<typename C::instance_type>::type p, Args... args)
  {
    ObjectClass *oc = ObjectClass::instance((GObject *)p);
    C *c = dynamic_cast<C *>(oc);
    if (!oc) {
      // connection already severed by heap destruction
      // use refptr instead of stack allocation if this is a problem
      g_error("missing object");
    } else if (!c) {
      // on our way to crash anyway
      g_error("wrong object type");
    }
    return ((c)->*(m))(std::forward<Args>(args)...);
  }
};

//// property handling ////

class PropertyBase
{
  typedef PropertyBase self_type;

  // subclass handles this
  virtual void set_property(const GValue *value) = 0;
  virtual void get_property(GValue *value) = 0;

  static GQuark get_instance_quark(guint prop_id)
  {
    auto str = std::string("GI__") +
               std::to_string((unsigned long)(&class_init)) + '_' +
               std::to_string(prop_id);
    return g_quark_from_string(str.c_str());
  }

  static PropertyBase *get_instance(
      GObject *object, GParamSpec *pspec, guint prop_id)
  {
    auto impl = ObjectClass::instance(object);
    auto poffset = g_param_spec_get_qdata(pspec, get_instance_quark(prop_id));
    return poffset ? (PropertyBase *)((char *)impl + GPOINTER_TO_INT(poffset))
                   : nullptr;
  }

  static void get_property(
      GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
  {
    auto prop = get_instance(object, pspec, prop_id);
    if (prop) {
      prop->get_property(value);
    } else {
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    }
  }

  static void set_property(
      GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
  {
    auto prop = get_instance(object, pspec, prop_id);
    if (prop) {
      prop->set_property(value);
    } else {
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    }
  }

protected:
  static GQuark get_prop_quark(const char *name)
  {
    auto str = std::string("GI__") + name;
    return g_quark_from_string(str.c_str());
  }

public:
  static void install_property(ObjectClass *impl, GObjectClass *klass,
      self_type *self, ParamSpec pspec, const gi::cstring_v name = "")
  {
    g_return_if_fail(impl);
    g_return_if_fail(klass);
    // need at least 1 parameter
    g_return_if_fail(pspec || name.size());

    // identify any property tracked by this PropertyBase code
    // (may or may not be unique process-wise)
    static guint prop_id;

    auto pname = name;
    if (pspec) {
      // mind transfer full
      g_object_class_install_property(klass, ++prop_id, pspec.gobj_copy_());
      // normalize name
      pname = pspec.name_();
    } else {
      g_object_class_override_property(klass, ++prop_id, name.c_str());
    }

    // mark property installed on this klass/type (by whatever code path)
    // sadly, in case of overridden properties,
    // glib does not expose sufficient info on whether it is already further up
    // or the override that we may be trying to add to this/our class
    // so, setup some parallel tracking using qdata
    auto gtype = G_OBJECT_CLASS_TYPE(klass);
    g_type_set_qdata(
        gtype, get_prop_quark(pname.c_str()), GINT_TO_POINTER(prop_id));

    // self may be absent if only registering type
    if (self) {
      install_property_offset(impl, klass, self, pname, prop_id);
    }
  }

  static void install_property_offset(ObjectClass *impl, GObjectClass *klass,
      self_type *self, const gi::cstring_v name, guint prop_id)
  {
    g_return_if_fail(self);

    // add metadata to pspec to retrieve upon set/get
    // an overridden one will be passed to get/set
    // (and returned by find as well)
    auto gpspec = g_object_class_find_property(klass, name.c_str());
    g_assert(gpspec);
    auto quark = get_instance_quark(prop_id);
    // NOTE no race/issue if multiple threads set this (to same value)
    // (internal locks are used by glib)
    if (!g_param_spec_get_qdata(gpspec, quark)) {
      auto offset = ((char *)self - (char *)impl);
      g_param_spec_set_qdata(gpspec, quark, GINT_TO_POINTER(offset));
    }
  }

  virtual ~PropertyBase() {}

  static void class_init(
      ObjectClass *impl, gpointer g_class, gpointer class_data)
  {
    auto klass = (GObjectClass *)g_class;
    auto props = (properties *)class_data;

    klass->get_property = get_property;
    klass->set_property = set_property;

    // need to add override properties at this point
    // (before creation of any instance or class struct)
    for (auto &&e : *props)
      install_property(impl, klass, e.second.first, e.second.second, e.first);
  }
};

inline void
class_init_props_sigs(
    ObjectClass *impl, gpointer g_class, gpointer props, gpointer _sigs)
{
  g_return_if_fail(g_class);

  if (props)
    PropertyBase::class_init(impl, g_class, props);

  // handle signals right here
  if (_sigs) {
    auto sigs = (signals *)_sigs;
    for (auto &sig : *sigs) {
      auto gtype = G_OBJECT_CLASS_TYPE(g_class);
      g_signal_newv(sig.name.c_str(), gtype, (GSignalFlags)sig.flags, nullptr,
          nullptr, nullptr, nullptr, sig.ret_type, sig.types.size(),
          sig.types.data());
    }
  }
}

template<typename T>
class property : protected property_proxy<T>, public PropertyBase
{
  T val_;

  void add_property(ObjectClass *impl, gi::cstring_v name)
  {
    auto pspec = Object::find_property(impl->gobj_klass_type(), name);
    // could have been defined already upon prior object creation
    if (pspec) {
      this->pspec_ = pspec;
      // normalize name
      name = pspec.name_();
      // could be defined by a superclass
      // or already added to this class by prior instance
      auto gtype = G_OBJECT_CLASS_TYPE(impl->gobj_klass());
      if (auto pd = g_type_get_qdata(gtype, get_prop_quark(name.c_str()))) {
        install_property_offset(
            impl, impl->gobj_klass(), this, name, GPOINTER_TO_INT(pd));
      } else {
        install_property(impl, impl->gobj_klass(), this, nullptr, name);
      }
    } else {
      pspec = this->pspec_;
      install_property(impl, impl->gobj_klass(), this, pspec);
    }
    // set value to default param value
    Value value;
    value.init<T>();
    g_param_value_set_default(pspec.gobj_(), &value);
    val_ = detail::get_value<T>(&value);
    // avoid circular ref loop
    if (this->object_)
      g_object_unref(this->object_.gobj_());
  }

protected:
  void set_property(const GValue *value) override
  {
    val_ = detail::get_value<T>(value);
  }

  void get_property(GValue *value) override { detail::set_value(value, val_); }

public:
  template<typename... Args,
      typename std::enable_if<sizeof...(Args) != 1>::type * = nullptr>
  property(ObjectClass *impl, Args &&...args)
      : property_proxy<T>(
            (Object)(*impl), ParamSpec::new_<T>(std::forward<Args>(args)...))
  {
    add_property(impl, this->pspec_.get_name());
  }

  property(ObjectClass *impl, const gi::cstring_v name)
      : property_proxy<T>((Object)(*impl), ParamSpec())
  {
    add_property(impl, name);
  }

  ~property()
  {
    // clear link
    this->object_.release_();
  }

  void notify()
  {
    g_object_notify_by_pspec(this->object_.gobj_(), this->pspec_.gobj_());
  }

  PropertyBase &operator=(T value)
  {
    val_ = value;
    notify();
    return *this;
  }

  void set_value(T value, bool _notify = true)
  {
    val_ = value;
    if (_notify)
      notify();
  }

  T get_value() const { return val_; }

  operator T() const { return val_; }

  property_proxy<T> get_proxy() const { return *this; }
};

template<typename T>
class property_read : public property<T>
{
public:
  template<typename... Args>
  property_read(ObjectClass *impl, Args &&...args)
      : property<T>(
            impl, std::forward<Args>(args)..., ParamFlags(G_PARAM_READABLE))
  {}

  property_proxy_read<T> get_proxy() const
  {
    return {this->object_, this->pspec_};
  }
};

template<typename T>
class property_write : public property<T>
{
public:
  template<typename... Args>
  property_write(ObjectClass *impl, Args &&...args)
      : property<T>(
            impl, std::forward<Args>(args)..., ParamFlags(G_PARAM_WRITABLE))
  {}

  property_proxy_write<T> get_proxy() const
  {
    return {this->object_, this->pspec_};
  }
};

//// signal handling ////

template<typename R, typename Instance, typename... Args, typename Base>
class signal<R(Instance, Args...), Base>
    : public signal_proxy<R(Instance, Args...)>
{
  typedef signal_proxy<R(Instance, Args...)> super;

  static guint new_(const gi::cstring_v name, GType itype, SignalFlags flags)
  {
    // collect array of types
    GType types[] = {traits::gtype<Args>::get_type()...};
    const int nparams = sizeof...(Args);
    const GType ret_type = traits::gtype<R>::get_type();
    // TODO accumulator
    return g_signal_newv(name.c_str(), itype, (GSignalFlags)flags, nullptr,
        nullptr, nullptr, nullptr, ret_type, nparams, types);
  }

public:
  signal(ObjectClass *owner, const gi::cstring_v name,
      SignalFlags flags = (SignalFlags)0)
      : super(*owner, name)
  {
    const GType itype = owner->gobj_klass_type();
    if (!g_signal_lookup(name.c_str(), itype))
      new_(name, itype, flags);
    // sneak away ref to avoid ref loop
    if (this->object_)
      g_object_unref(this->object_.gobj_());
  }

  ~signal()
  {
    // clear link
    this->object_.release_();
  }
};

template<typename T>
struct ObjectDeleter
{
  void operator()(T *ob)
  {
    if (ob->gobj_())
      g_object_unref(ob->gobj_());
  }
};

template<typename T>
class ref_ptr : public std::unique_ptr<T, ObjectDeleter<T>>
{
  typedef std::unique_ptr<T, ObjectDeleter<T>> super;
  typedef typename T::baseclass_type baseclass_type;

public:
  ref_ptr(T *ptr = nullptr, bool own = true) : super(ptr)
  {
    if (ptr && !own)
      g_object_ref(ptr->gobj_());
  }

  ref_ptr(ref_ptr &&other) = default;
  ref_ptr(const ref_ptr &other) : super(nullptr)
  {
    if (other)
      g_object_ref(other->gobj_());
    this->reset(other.get());
  }

  ref_ptr &operator=(ref_ptr &&other) = default;
  ref_ptr &operator=(const ref_ptr &other)
  {
    if (other && &other != this)
      g_object_ref(other->gobj_());
    this->reset(other.get());
    return *this;
  }

  operator baseclass_type() { return *this->get(); }
};

template<typename T, typename... Args>
ref_ptr<T>
make_ref_tagged(construct_cpp_t, Args &&...args)
{
  // move ownership of ref acquired during creation
  return ref_ptr<T>(new T(std::forward<Args>(args)...));
}

template<typename T, typename traits::if_valid_type<
                         typename T::baseclass_type>::type * = nullptr>
ref_ptr<T>
ref_ptr_cast(Object ob)
{
  if (ob) {
    ObjectClass *instance = ObjectClass::instance(ob.gobj_());
    if (instance) {
      auto obj = dynamic_cast<T *>(instance);
      if (obj) {
        // arrange to obtain an extra ref
        return ref_ptr<T>(obj, false);
      }
    }
  }
  return nullptr;
}

// similar to a typical glib xyz_get_type
// as both register the type (once) and return it (many times)
template<typename T>
GType
register_type()
{
  // C++11 standard specifies this should be MT-safe and occur only once
  // as such similar to the typical g_once in xyz_get_type
  static GType gtype = ObjectClass::register_type_<T>();
  return gtype;
}

template<typename T, typename... Args>
ref_ptr<T>
make_ref_tagged(construct_c_t, Args &&...args)
{
  auto gtype = register_type<T>();
  auto obj = Object::new_<::GObject *>(gtype, std::forward<Args>(args)...);
  return ref_ptr_cast<T>(obj);
}

// Construct can be specified explicitly;
// construct_c_t; also selected in auto case if supported,
//    Args specify construct properties (see also GObject::new_)
// construct_cpp_t; Args are forwarded to any applicable constructor
template<typename T, typename Construct = construct_auto_t, typename... Args>
ref_ptr<T>
make_ref(Args &&...args)
{
  using tag_t = typename std::conditional<
      std::is_same<Construct, construct_auto_t>::value,
      typename std::conditional<
          std::is_constructible<T, ObjectClass::InitData>::value, construct_c_t,
          construct_cpp_t>::type,
      Construct>::type;
  return make_ref_tagged<T>(tag_t(), std::forward<Args>(args)...);
}

} // namespace detail

// TODO impl namespace ??

using detail::property;
using detail::property_read;
using detail::property_write;
using detail::signal;

using detail::make_ref;
using detail::ref_ptr;
using detail::ref_ptr_cast;
using detail::register_type;

namespace repository
{
namespace GObject
{
namespace impl
{
// bring into namespaces as in code generation

namespace internal
{
using ObjectClass = detail::ObjectClass;

} // namespace internal

using ObjectImpl = detail::ObjectImpl<Object, detail::ObjectClass>;

} // namespace impl

} // namespace GObject

} // namespace repository

} // namespace gi

#endif // GI_OBJECTCLASS_HPP