|
107 | 107 | $(LREF SharedOf)
|
108 | 108 | ))
|
109 | 109 | $(TR $(TD Misc) $(TD
|
| 110 | + $(LREF defaultInit) |
110 | 111 | $(LREF EnumMembers)
|
111 | 112 | $(LREF lvalueOf)
|
112 | 113 | $(LREF rvalueOf)
|
@@ -1687,6 +1688,210 @@ if (__traits(isTemplate, Template))
|
1687 | 1688 | static assert(!isBar!(likeFoo!int));
|
1688 | 1689 | }
|
1689 | 1690 |
|
| 1691 | +/++ |
| 1692 | + Evaluates to the default-initialized value of the given type. |
| 1693 | +
|
| 1694 | + defaultInit should be used in generic code in contexts where the |
| 1695 | + default-initialized value of a type is needed rather than that type's |
| 1696 | + $(D init) value. |
| 1697 | +
|
| 1698 | + For most types, the default-initialized value of a type $(I is) its |
| 1699 | + $(D init) value - i.e. for some type, $(D T), it would normally be |
| 1700 | + $(D T.init). However, there are some corner cases in the language where a |
| 1701 | + type's $(D init) value is not its default-initialized value. In particular, |
| 1702 | +
|
| 1703 | + 1. If a type is a non-$(K_STATIC) nested struct, it has a context pointer |
| 1704 | + which refers to the scope in which it's declared. So, its |
| 1705 | + default-initialized value is its $(D init) value $(I plus) the value for |
| 1706 | + its context pointer. However, if such a nested struct is explicitly |
| 1707 | + initialized with just its $(D init) value instead of being |
| 1708 | + default-initialized or being constructed via a constructor, then its |
| 1709 | + context pointer is $(K_NULL), potentially leading to segfaults when the |
| 1710 | + object is used. |
| 1711 | + 2. If a type is a struct for which default initialization has been disabled |
| 1712 | + using $(D @disable this();), then while its $(D init) value is still used |
| 1713 | + as the value of the object at the start of a constructor call (as is the |
| 1714 | + case with any struct), the struct cannot be default-initialized and must |
| 1715 | + instead be explicitly constructed. So, instead of $(D T.init) being the |
| 1716 | + default-initialized value, it's just the struct's initial state before |
| 1717 | + the constructor constructs the object, and the struct does not actually |
| 1718 | + have a default-initialized value. |
| 1719 | +
|
| 1720 | + In the case of #2, there is no default initialization for the struct, |
| 1721 | + whereas in the case of #1, there $(I is) default initialization for the |
| 1722 | + struct but only within the scope where the struct is declared. Outside of |
| 1723 | + that scope, the compiler does not have access to that scope and therefore |
| 1724 | + cannot iniitialize the context pointer with a value, so a compilation error |
| 1725 | + results. And so, either case can make it so that a struct cannot be |
| 1726 | + default-initialized. |
| 1727 | +
|
| 1728 | + In both cases, an instance of the struct can still be explicitly |
| 1729 | + initialized with its $(D init) value, but that will usually lead to |
| 1730 | + incorrect code, because either the object's context pointer will be |
| 1731 | + $(K_NULL), or it's a type which was designed with the idea that it would |
| 1732 | + only ever be explicitly constructed. So, while sometimes it's still |
| 1733 | + appropriate to explicitly use the $(D init) value (e.g. by default, |
| 1734 | + $(REF1 destroy, object) will set the object to its $(D init) value after |
| 1735 | + destroying it so that it's in a valid state to have its destructor called |
| 1736 | + afterwards in cases where the object is still going to be destroyed when it |
| 1737 | + leaves scope), in general, generic code which needs to explicitly |
| 1738 | + default-initialize a variable shouldn't use the type's $(D init) value. |
| 1739 | +
|
| 1740 | + For a type, $(D T), which is a struct which does not declare a $(K_STATIC) |
| 1741 | + $(D opCall), $(D T()) can be used instead of $(D T.init) to get the type's |
| 1742 | + default-initialized value, and unlike $(D T.init), it will fail to compile |
| 1743 | + if the object cannot actually be default-initialized (be it because it has |
| 1744 | + disabled default initialization, or because it's a nested struct outside of |
| 1745 | + the scope where that struct was declared). So, unlike $(D T.init), it |
| 1746 | + won't compile in cases where default initialization does not work, and thus |
| 1747 | + it can't accidentally be used to initialize a struct which cannot be |
| 1748 | + default-intiialized. Also, for nested structs, $(D T()) will initialize the |
| 1749 | + context pointer, unlike $(D T.init). So, using $(D T()) normally gives the |
| 1750 | + actual default-initialized value for the type or fails to compile if the |
| 1751 | + type cannot be default-initialized. |
| 1752 | +
|
| 1753 | + However, unfortunately, using $(D T()) does not work in generic code, |
| 1754 | + because it is legal for a struct to declare a $(K_STATIC) $(D opCall) which |
| 1755 | + takes no arguments, overriding the normal behavior of $(D T()) and making |
| 1756 | + it so that it's no longer the default-initialized value. Instead, it's |
| 1757 | + whatever $(K_STATIC) $(D opCall) returns, and $(K_STATIC) $(D opCall) can |
| 1758 | + return any type, not just $(D T), because even though it looks like a |
| 1759 | + constructor call, it's not actually a constructor call, and it can return |
| 1760 | + whatever the programmer felt like - including $(K_VOID). This means that |
| 1761 | + the only way to consistently get a default-initialized value for a type in |
| 1762 | + generic code is to actually declare a variable and not give it a value. |
| 1763 | +
|
| 1764 | + So, in order to work around that, defaultInit does that for you. |
| 1765 | + $(D defaultInit!Foo) evaluates to the default-initialized value of $(D Foo), |
| 1766 | + but unlike $(D Foo.init), it won't compile when $(D Foo) cannot be |
| 1767 | + default-initialized. And it won't get hijacked by $(K_STATIC) $(D opCall). |
| 1768 | +
|
| 1769 | + The downside to using such a helper template is that it will not work with |
| 1770 | + a nested struct even within the scope where that nested struct is declared |
| 1771 | + (since defaultInit is declared outside of that scope). So, code which needs |
| 1772 | + to get a default-initialized instance of a nested struct within the scope |
| 1773 | + where it's declared will either have to simply declare a variable and not |
| 1774 | + initialize it or use $(D T()) to explicitly get the default-initialized |
| 1775 | + value. And since this is within the code where the type is declared, it's |
| 1776 | + fully within the programmer's control to not declare a $(K_STATIC) |
| 1777 | + $(D opCall) for it. So, it shouldn't be a problem in practice. |
| 1778 | + +/ |
| 1779 | +template defaultInit(T) |
| 1780 | +if (is(typeof({T t;}))) |
| 1781 | +{ |
| 1782 | + // At present, simply using T.init should work, since all of the cases where |
| 1783 | + // it wouldn't won't get past the template constraint. However, it's |
| 1784 | + // possible that that will change at some point in the future, and this |
| 1785 | + // approach is guaranteed to give whatever the default-initialized value is |
| 1786 | + // regardless of whether it's T.init. |
| 1787 | + enum defaultInit = (){ T retval; return retval; }(); |
| 1788 | +} |
| 1789 | + |
| 1790 | +/// |
| 1791 | +@safe unittest |
| 1792 | +{ |
| 1793 | + static assert(defaultInit!int == 0); |
| 1794 | + static assert(defaultInit!bool == false); |
| 1795 | + static assert(defaultInit!(int*) is null); |
| 1796 | + static assert(defaultInit!(int[]) is null); |
| 1797 | + static assert(defaultInit!string is null); |
| 1798 | + static assert(defaultInit!(int[2]) == [0, 0]); |
| 1799 | + |
| 1800 | + static struct S |
| 1801 | + { |
| 1802 | + int i = 42; |
| 1803 | + } |
| 1804 | + static assert(defaultInit!S == S(42)); |
| 1805 | + |
| 1806 | + static assert(defaultInit!Object is null); |
| 1807 | + |
| 1808 | + interface I |
| 1809 | + { |
| 1810 | + bool foo(); |
| 1811 | + } |
| 1812 | + static assert(defaultInit!I is null); |
| 1813 | + |
| 1814 | + static struct NoDefaultInit |
| 1815 | + { |
| 1816 | + int i; |
| 1817 | + @disable this(); |
| 1818 | + } |
| 1819 | + |
| 1820 | + // It's not legal to default-initialize NoDefaultInit, because it has |
| 1821 | + // disabled default initialization. |
| 1822 | + static assert(!__traits(compiles, defaultInit!NoDefaultInit)); |
| 1823 | + |
| 1824 | + int var = 2; |
| 1825 | + struct Nested |
| 1826 | + { |
| 1827 | + int i = 40; |
| 1828 | + |
| 1829 | + int foo() |
| 1830 | + { |
| 1831 | + return i + var; |
| 1832 | + } |
| 1833 | + } |
| 1834 | + |
| 1835 | + // defaultInit doesn't have access to this scope and thus cannot |
| 1836 | + // initialize the nested struct. |
| 1837 | + static assert(!__traits(compiles, defaultInit!Nested)); |
| 1838 | + |
| 1839 | + // However, because Nested has no static opCall (and we know it doesn't |
| 1840 | + // because we're doing this in the same scope where Nested was declared), |
| 1841 | + // Nested() can be used to get the default-initialized value. |
| 1842 | + static assert(Nested() == Nested(40)); |
| 1843 | + |
| 1844 | + Nested nested; |
| 1845 | + assert(Nested() == nested); |
| 1846 | + |
| 1847 | + // Both have properly initialized context pointers, |
| 1848 | + // whereas Nested.init does not. |
| 1849 | + assert(Nested().foo() == nested.foo()); |
| 1850 | + |
| 1851 | + // defaultInit does not get hijacked by static opCall. |
| 1852 | + static struct HasOpCall |
| 1853 | + { |
| 1854 | + int i; |
| 1855 | + |
| 1856 | + static opCall() |
| 1857 | + { |
| 1858 | + return HasOpCall(42); |
| 1859 | + } |
| 1860 | + |
| 1861 | + static opCall(int i) |
| 1862 | + { |
| 1863 | + HasOpCall retval; |
| 1864 | + retval.i = i; |
| 1865 | + return retval; |
| 1866 | + } |
| 1867 | + } |
| 1868 | + |
| 1869 | + static assert(defaultInit!HasOpCall == HasOpCall(0)); |
| 1870 | + static assert(HasOpCall() == HasOpCall(42)); |
| 1871 | +} |
| 1872 | + |
| 1873 | +@safe unittest |
| 1874 | +{ |
| 1875 | + static struct NoCopy |
| 1876 | + { |
| 1877 | + int i = 17; |
| 1878 | + @disable this(this); |
| 1879 | + } |
| 1880 | + static assert(defaultInit!NoCopy == NoCopy(17)); |
| 1881 | + |
| 1882 | + string function() funcPtr; |
| 1883 | + static assert(defaultInit!(SymbolType!funcPtr) is null); |
| 1884 | + |
| 1885 | + string delegate() del; |
| 1886 | + static assert(defaultInit!(SymbolType!del) is null); |
| 1887 | + |
| 1888 | + int function() @property propFuncPtr; |
| 1889 | + static assert(defaultInit!(SymbolType!propFuncPtr) is null); |
| 1890 | + |
| 1891 | + int delegate() @property propDel; |
| 1892 | + static assert(defaultInit!(SymbolType!propDel) is null); |
| 1893 | +} |
| 1894 | + |
1690 | 1895 | /++
|
1691 | 1896 | Evaluates to an $(D AliasSeq) containing the members of an enum type.
|
1692 | 1897 |
|
@@ -6697,7 +6902,7 @@ if (!is(sym))
|
6697 | 6902 | $(DDSUBLINK spec/traits, getOverloads, $(D __traits(getOverloads, ...))
|
6698 | 6903 | must be used.
|
6699 | 6904 |
|
6700 |
| - In general, $(getOverloads) should be used when using SymbolType, since |
| 6905 | + In general, $(D getOverloads) should be used when using SymbolType, since |
6701 | 6906 | there's no guarantee that the first one is the correct one (and often, code
|
6702 | 6907 | will need to check all of the overloads), whereas with PropertyType, it
|
6703 | 6908 | doesn't usually make sense to get specific overloads, because there can
|
|
0 commit comments